diff --git a/Makefile b/Makefile index e8ba9905ccc..5df58200c9f 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ lint: --exclude tests/PHPStan/Rules/Arrays/data/offset-access-without-dim-for-reading.php \ --exclude tests/PHPStan/Rules/Classes/data/bug-11891.php \ --exclude tests/PHPStan/Rules/Classes/data/bug-13768.php \ + --exclude tests/PHPStan/Rules/Classes/data/closure-bind-scope-nested-class.php \ --exclude tests/PHPStan/Rules/Classes/data/duplicate-declarations.php \ --exclude tests/PHPStan/Rules/Classes/data/duplicate-enum-cases.php \ --exclude tests/PHPStan/Rules/Classes/data/enum-sanity.php \ diff --git a/src/Analyser/ExprHandler/ClassConstFetchHandler.php b/src/Analyser/ExprHandler/ClassConstFetchHandler.php index aff729cfbbc..f490a8bda65 100644 --- a/src/Analyser/ExprHandler/ClassConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ClassConstFetchHandler.php @@ -23,7 +23,11 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; use PHPStan\Turbo\ShadowedByTurboExtension; +use PHPStan\Type\ClassStringType; +use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\MixedType; +use PHPStan\Type\ObjectType; +use PHPStan\Type\StaticType; use PHPStan\Type\Type; use function array_merge; @@ -84,7 +88,28 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // the enclosing class is lexical - fixed at this node, identical on every // (possibly narrowed) scope the callback may later be invoked with - so // resolve it once here instead of reading it off the callback's scope. - $classReflection = $beforeScope->isInClass() ? $beforeScope->getClassReflection() : null; + // Inside a closure scoped by Closure::bind(), self/parent/static name the bound class; + // bound to a class that is not exactly one known class, they name none. + $classReflection = $beforeScope->getClosureBindScopeClassReflection(); + if ($classReflection === null && $beforeScope->isInClass() && !$beforeScope->isClosureBindScopeClassAmbiguous()) { + $classReflection = $beforeScope->getClassReflection(); + } + // ...and their ::class is a class-string of the closest class all candidates extend + $ambiguousClassStringType = null; + if ( + $expr->class instanceof Name + && $expr->class->isSpecialClassName() + && $expr->name instanceof Identifier + && $expr->name->toLowerString() === 'class' + && $beforeScope->isClosureBindScopeClassAmbiguous() + ) { + $commonAncestor = $beforeScope->getClosureBindScopeCommonAncestor($expr->class); + if ($commonAncestor === null) { + $ambiguousClassStringType = new ClassStringType(); + } else { + $ambiguousClassStringType = new GenericClassStringType($expr->class->toLowerString() === 'static' ? new StaticType($commonAncestor) : new ObjectType($commonAncestor->getName())); + } + } $result = $this->expressionResultFactory->create( $scope, @@ -95,10 +120,13 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints, - typeCallback: function (bool $nativeTypesPromoted) use ($expr, $classResult, $classReflection): Type { + typeCallback: function (bool $nativeTypesPromoted) use ($expr, $classResult, $classReflection, $ambiguousClassStringType): Type { if (!$expr->name instanceof Identifier) { return new MixedType(); } + if ($ambiguousClassStringType !== null) { + return $ambiguousClassStringType; + } return $this->initializerExprTypeResolver->getClassConstFetchTypeByReflection( $expr->class, diff --git a/src/Analyser/ExprHandler/NewHandler.php b/src/Analyser/ExprHandler/NewHandler.php index 61244aa9fbe..ddb30939178 100644 --- a/src/Analyser/ExprHandler/NewHandler.php +++ b/src/Analyser/ExprHandler/NewHandler.php @@ -68,6 +68,7 @@ use PHPStan\Type\NeverType; use PHPStan\Type\NonexistentParentClassType; use PHPStan\Type\ObjectType; +use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StaticType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -504,6 +505,17 @@ private function resolveReturnType(MutatingScope $scope, Expr $expr, ?Parameters */ private function exactInstantiation(MutatingScope $scope, New_ $node, Name $className, ?ParametersAcceptor $preResolvedAcceptor, ?ArgsResult $argsResult = null, bool $allowUnresolved = true): Type { + if ($className->isSpecialClassName() && $scope->isClosureBindScopeClassAmbiguous()) { + // bound to a class that is not exactly one known class: an instance of the + // closest class all candidates extend, or of some class + $commonAncestor = $scope->getClosureBindScopeCommonAncestor($className); + if ($commonAncestor === null) { + return new ObjectWithoutClassType(); + } + + return $className->toLowerString() === 'static' ? new StaticType($commonAncestor) : new ObjectType($commonAncestor->getName()); + } + $resolvedClassName = $scope->resolveName($className); $isStatic = false; $lowercasedClassName = $className->toLowerString(); diff --git a/src/Analyser/ExprHandler/StaticCallHandler.php b/src/Analyser/ExprHandler/StaticCallHandler.php index a90ba1d98b2..06a600bb34b 100644 --- a/src/Analyser/ExprHandler/StaticCallHandler.php +++ b/src/Analyser/ExprHandler/StaticCallHandler.php @@ -44,12 +44,14 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; use PHPStan\Turbo\ShadowedByTurboExtension; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Generic\TemplateTypeVarianceMap; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; +use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; use PHPStan\Type\StaticType; use PHPStan\Type\Type; @@ -156,7 +158,10 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $declaringClass->getName() === 'Closure' && strtolower($methodName) === 'bind' ) { - $closureBindScopeFactory = static function (MutatingScope $boundScope) use ($expr, $storage): MutatingScope { + $closureBindScopeFactory = static function (MutatingScope $boundScope) use ($expr, $storage, $parametersAcceptor): MutatingScope { + // normalized so that $newThis and $newScope are found at their + // parameter positions even when the call names its arguments + $expr = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $expr) ?? $expr; // invoked while the closure argument is walked; the other // arguments were processed before it (processArgs orders // closures last), so their results are already stored. A @@ -183,9 +188,13 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } } $scopeClasses = ['static']; + $bindsUnknownScopeClass = false; if (isset($expr->getArgs()[2])) { $argValue = $expr->getArgs()[2]->value; $argValueType = $readArgType($argValue, false); + // a newScope that may name a class binds to one even when it is unknown; + // 'static' and null name none + $bindsUnknownScopeClass = !TypeCombinator::union(new ConstantStringType('static'), new NullType())->isSuperTypeOf($argValueType)->yes(); $directClassNames = $argValueType->getObjectClassNames(); if (count($directClassNames) > 0) { @@ -200,6 +209,9 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scopeClasses = $thisType->getObjectClassNames(); } } + if ($scopeClasses === [] && $bindsUnknownScopeClass) { + $scopeClasses = [MutatingScope::UNKNOWN_CLOSURE_BIND_SCOPE_CLASS]; + } return $boundScope->enterClosureBind($thisType, $nativeThisType, $scopeClasses); }; } diff --git a/src/Analyser/InternalScopeFactory.php b/src/Analyser/InternalScopeFactory.php index ee2c58961c0..29015341ebf 100644 --- a/src/Analyser/InternalScopeFactory.php +++ b/src/Analyser/InternalScopeFactory.php @@ -17,7 +17,7 @@ interface InternalScopeFactory * @param array $expressionTypes * @param array $nativeExpressionTypes * @param array $conditionalExpressions - * @param list $inClosureBindScopeClasses + * @param list $inClosureBindScopeClasses 'static' and MutatingScope::UNKNOWN_CLOSURE_BIND_SCOPE_CLASS included * @param array $currentlyAssignedExpressions * @param array $currentlyAllowedUndefinedExpressions * @param list $inFunctionCallsStack diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 85867200a20..f579b4744b5 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -141,6 +141,13 @@ class MutatingScope implements Scope, NodeCallbackInvoker, CollectedDataEmitter, DependencyTracker { + /** + * Stands for the class of a Closure::bind() newScope whose class is not known (a + * class-string or string without a class name). It is never a class name, so the + * scope stays bound - unlike an empty list - without resolving to any class. + */ + public const UNKNOWN_CLOSURE_BIND_SCOPE_CLASS = '*'; + private const COMPLEX_UNION_TYPE_MEMBER_LIMIT = 8; /** Distinct name/namespace combinations getGlobalConstantType() remembers the expression keys of. */ @@ -179,7 +186,7 @@ class MutatingScope implements Scope, NodeCallbackInvoker, CollectedDataEmitter, /** * @param array $expressionTypes * @param array $conditionalExpressions - * @param list $inClosureBindScopeClasses + * @param list $inClosureBindScopeClasses the classes a Closure::bind()/call() scope is bound to: 'static' for the closure's own outside a class, {@see UNKNOWN_CLOSURE_BIND_SCOPE_CLASS} for a class that is not known * @param array $currentlyAssignedExpressions true when the expression is a plain write target (its writable type applies), false when it is read-modified in place (e.g. the base of `$prop[] = ...`), where its readable type applies * @param array $currentlyAllowedUndefinedExpressions * @param array $nativeExpressionTypes @@ -1637,21 +1644,32 @@ private function promoteNativeTypes(): self public function resolveName(Name $name): string { $originalClass = (string) $name; - if ($this->isInClass()) { - $lowerClass = strtolower($originalClass); - if (in_array($lowerClass, [ - 'self', - 'static', - ], true)) { - if ($this->inClosureBindScopeClasses !== [] && $this->inClosureBindScopeClasses !== ['static']) { - return $this->inClosureBindScopeClasses[0]; - } + $lowerClass = strtolower($originalClass); + + if (in_array($lowerClass, ['self', 'static', 'parent'], true) && $this->isClosureBindScopeClassAmbiguous()) { + // bound to a class that is not exactly one known class: neither the enclosing + // class nor any one of the candidates + return $originalClass; + } + + $closureBindScopeClassName = $this->getClosureBindScopeClassName(); + if (in_array($lowerClass, [ + 'self', + 'static', + ], true)) { + if ($closureBindScopeClassName !== null) { + return $closureBindScopeClassName; + } + if ($this->isInClass()) { return $this->getClassReflection()->getName(); - } elseif ($lowerClass === 'parent') { + } + } elseif ($lowerClass === 'parent') { + $currentClassReflection = $this->getClosureBindScopeClassReflection(); + if ($currentClassReflection === null && $closureBindScopeClassName === null && $this->isInClass()) { $currentClassReflection = $this->getClassReflection(); - if ($currentClassReflection->getParentClass() !== null) { - return $currentClassReflection->getParentClass()->getName(); - } + } + if ($currentClassReflection !== null && $currentClassReflection->getParentClass() !== null) { + return $currentClassReflection->getParentClass()->getName(); } } @@ -1661,25 +1679,40 @@ public function resolveName(Name $name): string /** @api */ public function resolveTypeByName(Name $name): TypeWithClassName { - if ($name->toLowerString() === 'static' && $this->isInClass()) { - if ($this->inClosureBindScopeClasses !== [] && $this->inClosureBindScopeClasses !== ['static']) { - if ($this->services->reflectionProvider->hasClass($this->inClosureBindScopeClasses[0])) { - return new StaticType($this->services->reflectionProvider->getClass($this->inClosureBindScopeClasses[0])); - } + if ($name->toLowerString() === 'static') { + $closureBindScopeClassReflection = $this->getClosureBindScopeClassReflection(); + if ($closureBindScopeClassReflection !== null) { + return new StaticType($closureBindScopeClassReflection); + } + if ($this->isInClass()) { + return new StaticType($this->getClassReflection()); + } + } + + if ($name->isSpecialClassName() && $this->isClosureBindScopeClassAmbiguous()) { + // bound to a class that is not exactly one known class: the closest class all + // candidates are or extend, if any + $commonAncestor = $this->getClosureBindScopeCommonAncestor($name); + if ($commonAncestor !== null) { + return $name->toLowerString() === 'static' ? new StaticType($commonAncestor) : new ObjectType($commonAncestor->getName()); } - return new StaticType($this->getClassReflection()); + return new ObjectType((string) $name); } $originalClass = $this->resolveName($name); - if ($this->isInClass()) { - if ($this->inClosureBindScopeClasses === [$originalClass]) { - if ($this->services->reflectionProvider->hasClass($originalClass)) { - return new ThisType($this->services->reflectionProvider->getClass($originalClass)); - } - return new ObjectType($originalClass); + // outside a class only self/parent/static follow the Closure::bind() scope; + // inside one any name of the bound class does, as before + if ( + $this->inClosureBindScopeClasses === [$originalClass] + && ($this->isInClass() || $name->isSpecialClassName()) + ) { + if ($this->services->reflectionProvider->hasClass($originalClass)) { + return new ThisType($this->services->reflectionProvider->getClass($originalClass)); } - + return new ObjectType($originalClass); + } + if ($this->isInClass()) { $thisType = new ThisType($this->getClassReflection()); $ancestor = $thisType->getAncestorWithClassName($originalClass); if ($ancestor !== null) { @@ -1690,6 +1723,94 @@ public function resolveTypeByName(Name $name): TypeWithClassName return new ObjectType($originalClass); } + /** + * The class a `Closure::bind()` / `Closure::call()` scoped this scope to, as + * self/parent/static see it: null when the scope is not bound to a class (unbound, + * or bound to the default "static" scope outside a class). Holds inside and outside + * of a class; entering a class-like resets it. + * + * @return non-empty-string|null + */ + private function getClosureBindScopeClassName(): ?string + { + if ( + $this->inClosureBindScopeClasses === [] + || $this->inClosureBindScopeClasses === ['static'] + || $this->isClosureBindScopeClassAmbiguous() + ) { + return null; + } + + return $this->inClosureBindScopeClasses[0]; + } + + /** + * Whether this scope is bound by Closure::bind() to a class that is not exactly one + * known class: one of several classes, or a class-string/string newScope whose class + * is unknown. self/parent/static then name some class that cannot be checked. + */ + public function isClosureBindScopeClassAmbiguous(): bool + { + return count($this->inClosureBindScopeClasses) > 1 + || in_array(self::UNKNOWN_CLOSURE_BIND_SCOPE_CLASS, $this->inClosureBindScopeClasses, true); + } + + /** + * In a scope bound to a class that is not exactly one known class + * ({@see isClosureBindScopeClassAmbiguous()}): the closest class all candidates for + * `self`/`static` - or, for `parent`, all their parent classes - are or extend. Null + * otherwise, and when a candidate is unknown or the candidates share no class. + */ + public function getClosureBindScopeCommonAncestor(Name $name): ?ClassReflection + { + if (!$name->isSpecialClassName() || !$this->isClosureBindScopeClassAmbiguous()) { + return null; + } + + $isParent = $name->toLowerString() === 'parent'; + $classReflections = []; + foreach ($this->inClosureBindScopeClasses as $className) { + if ($className === self::UNKNOWN_CLOSURE_BIND_SCOPE_CLASS || !$this->services->reflectionProvider->hasClass($className)) { + return null; + } + $classReflection = $this->services->reflectionProvider->getClass($className); + if ($isParent) { + $classReflection = $classReflection->getParentClass(); + if ($classReflection === null) { + return null; + } + } + $classReflections[] = $classReflection; + } + + $candidate = $classReflections[0]; + while ($candidate !== null) { + foreach ($classReflections as $classReflection) { + if ($classReflection->getName() !== $candidate->getName() && !$classReflection->isSubclassOfClass($candidate)) { + $candidate = $candidate->getParentClass(); + continue 2; + } + } + + return $candidate; + } + + return null; + } + + /** + * The reflection of {@see getClosureBindScopeClassName()}, null also when the class is unknown. + */ + public function getClosureBindScopeClassReflection(): ?ClassReflection + { + $className = $this->getClosureBindScopeClassName(); + if ($className === null || !$this->services->reflectionProvider->hasClass($className)) { + return null; + } + + return $this->services->reflectionProvider->getClass($className); + } + /** * @api * @param mixed $value @@ -2277,7 +2398,7 @@ public function enterNamespace(string $namespaceName): self } /** - * @param list $scopeClasses + * @param list $scopeClasses the classes newScope names; ['static'] keeps the closure's own, [UNKNOWN_CLOSURE_BIND_SCOPE_CLASS] a class that is not known */ public function enterClosureBind(?Type $thisType, ?Type $nativeThisType, array $scopeClasses): self { @@ -2409,13 +2530,26 @@ public function enterClosureCall(Type $thisType, Type $nativeThisType): self $expressionTypes, $nativeExpressionTypes, $this->conditionalExpressions, - $thisType->getObjectClassNames(), + self::closureBindScopeClassesOf($thisType->getObjectClassNames()), $this->anonymousFunctionReflection, templateArgumentFrame: $this->templateArgumentFrame, templateArgumentConstraints: $this->templateArgumentConstraints, ); } + /** + * The scope classes of a closure bound to an object of these classes (Closure::call(), + * a `@param-closure-this` type through withClosureBindScopeClasses()): an object of no + * known class binds it to an unknown class ({@see UNKNOWN_CLOSURE_BIND_SCOPE_CLASS}). + * + * @param list $classNames + * @return non-empty-list + */ + private static function closureBindScopeClassesOf(array $classNames): array + { + return $classNames === [] ? [self::UNKNOWN_CLOSURE_BIND_SCOPE_CLASS] : $classNames; + } + /** @api */ public function isInClosureBind(): bool { @@ -2435,7 +2569,7 @@ public function withClosureBindScopeClasses(array $scopeClasses): self $this->expressionTypes, $this->nativeExpressionTypes, $this->conditionalExpressions, - $scopeClasses, + self::closureBindScopeClassesOf($scopeClasses), $this->anonymousFunctionReflection, $this->isInFirstLevelStatement(), $this->currentlyAssignedExpressions, @@ -2792,21 +2926,30 @@ public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type false, )), new AccessoryArrayListType()]); } - if ( - $type instanceof Name - && $this->inClosureBindScopeClasses !== [] - && $this->inClosureBindScopeClasses !== ['static'] - && in_array($type->toLowerString(), ['static', 'self', 'parent'], true) - && $this->services->reflectionProvider->hasClass($this->inClosureBindScopeClasses[0]) - ) { - return $this->services->initializerExprTypeResolver->getFunctionType( - $type, - $isNullable, - false, - InitializerExprContext::fromClassReflection( - $this->services->reflectionProvider->getClass($this->inClosureBindScopeClasses[0]), - ), - ); + if ($type instanceof Name && $type->isSpecialClassName()) { + if ($this->isClosureBindScopeClassAmbiguous()) { + // bound to a class that is not exactly one known class: the closest class all + // candidates are or extend, or nothing known + $commonAncestor = $this->getClosureBindScopeCommonAncestor($type); + if ($commonAncestor === null) { + return new MixedType(); + } + if ($type->toLowerString() === 'static') { + return $this->services->initializerExprTypeResolver->getFunctionType($type, $isNullable, false, InitializerExprContext::fromClassReflection($commonAncestor)); + } + + return $this->services->initializerExprTypeResolver->getFunctionType(new Name\FullyQualified($commonAncestor->getName()), $isNullable, false, InitializerExprContext::fromScope($this)); + } + + $closureBindScopeClassReflection = $this->getClosureBindScopeClassReflection(); + if ($closureBindScopeClassReflection !== null) { + return $this->services->initializerExprTypeResolver->getFunctionType( + $type, + $isNullable, + false, + InitializerExprContext::fromClassReflection($closureBindScopeClassReflection), + ); + } } return $this->services->initializerExprTypeResolver->getFunctionType($type, $isNullable, false, InitializerExprContext::fromScope($this)); @@ -5538,6 +5681,10 @@ public function canWriteProperty(ExtendedPropertyReflection $propertyReflection) return $propertyReflection->getDeclaringClass()->isSubclassOfClass($classReflection); }; + if (in_array(self::UNKNOWN_CLOSURE_BIND_SCOPE_CLASS, $this->inClosureBindScopeClasses, true)) { + // bound to an unknown class, which may be the one declaring the member + return true; + } foreach ($this->inClosureBindScopeClasses as $inClosureBindScopeClass) { if (!$this->services->reflectionProvider->hasClass($inClosureBindScopeClass)) { continue; @@ -5595,6 +5742,10 @@ private function canAccessClassMember(ClassMemberReflection $classMemberReflecti return $classMemberReflection->getDeclaringClass()->isSubclassOfClass($classReflection); }; + if (in_array(self::UNKNOWN_CLOSURE_BIND_SCOPE_CLASS, $this->inClosureBindScopeClasses, true)) { + // bound to an unknown class, which may be the one declaring the member + return true; + } foreach ($this->inClosureBindScopeClasses as $inClosureBindScopeClass) { if (!$this->services->reflectionProvider->hasClass($inClosureBindScopeClass)) { continue; diff --git a/src/Parser/ClosureBindArgVisitor.php b/src/Parser/ClosureBindArgVisitor.php index 93c22b7c615..9a0ac5a9b25 100644 --- a/src/Parser/ClosureBindArgVisitor.php +++ b/src/Parser/ClosureBindArgVisitor.php @@ -8,7 +8,6 @@ use PhpParser\NodeVisitorAbstract; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Turbo\ShadowedByTurboExtension; -use function count; #[AutowiredService] #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/ClosureBindArgVisitor.cpp')] @@ -28,11 +27,32 @@ public function enterNode(Node $node): ?Node && $node->name->toLowerString() === 'bind' && !$node->isFirstClassCallable() ) { - $args = $node->getArgs(); - if (count($args) > 1) { - $args[0]->setAttribute(self::ATTRIBUTE_NAME, true); + $closureArg = null; + $newThisArg = null; + foreach ($node->getArgs() as $i => $arg) { + if ($arg->name === null) { + if ($i === 0) { + $closureArg = $arg; + } elseif ($i === 1) { + $newThisArg = $arg; + } + continue; + } + + // a duplicate named argument does not replace the one already + // found, like in ArgumentsNormalizer::reorderArgs() + if ($arg->name->toString() === 'closure') { + $closureArg ??= $arg; + } elseif ($arg->name->toString() === 'newThis') { + $newThisArg ??= $arg; + } + } + + if ($closureArg !== null && $newThisArg !== null) { + $closureArg->setAttribute(self::ATTRIBUTE_NAME, true); } } + return null; } diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 22c137ede82..722b3a09e1f 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -532,12 +532,22 @@ public static function applyIntrinsicArgOverrides( } } + // the closure is the 1st argument unless the call names its arguments + $closureBindArg = $args[0]; + if ($closureBindArg->name !== null) { + foreach ($args as $arg) { + if ($arg->getAttribute(ClosureBindArgVisitor::ATTRIBUTE_NAME) !== null) { + $closureBindArg = $arg; + break; + } + } + } if ( - $args[0]->getAttribute(ClosureBindArgVisitor::ATTRIBUTE_NAME) !== null - && $args[0]->value instanceof Node\Expr\Variable - && is_string($args[0]->value->name) + $closureBindArg->getAttribute(ClosureBindArgVisitor::ATTRIBUTE_NAME) !== null + && $closureBindArg->value instanceof Node\Expr\Variable + && is_string($closureBindArg->value->name) ) { - $closureVarName = $args[0]->value->name; + $closureVarName = $closureBindArg->value->name; $inFunction = $scope->getFunction(); if ($inFunction !== null) { $closureThisParameters = []; diff --git a/src/Rules/Classes/ClassConstantRule.php b/src/Rules/Classes/ClassConstantRule.php index ba307f16227..2f9d7bbce89 100644 --- a/src/Rules/Classes/ClassConstantRule.php +++ b/src/Rules/Classes/ClassConstantRule.php @@ -19,6 +19,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; +use PHPStan\Rules\SelfClassResolver; use PHPStan\Type\ErrorType; use PHPStan\Type\StringType; use PHPStan\Type\ThisType; @@ -94,8 +95,12 @@ private function processSingleClassConstFetch(Scope $scope, ClassConstFetch $nod if ($class instanceof Node\Name) { $className = (string) $class; $lowercasedClassName = strtolower($className); + if (in_array($lowercasedClassName, ['self', 'static', 'parent'], true) && SelfClassResolver::isAmbiguous($scope)) { + return []; + } + $selfClassReflection = SelfClassResolver::resolve($scope, $this->reflectionProvider); if (in_array($lowercasedClassName, ['self', 'static'], true)) { - if (!$scope->isInClass()) { + if ($selfClassReflection === null) { return [ RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $className)) ->identifier(sprintf('outOfClass.%s', $lowercasedClassName)) @@ -105,20 +110,19 @@ private function processSingleClassConstFetch(Scope $scope, ClassConstFetch $nod $classType = $scope->resolveTypeByName($class); } elseif ($lowercasedClassName === 'parent') { - if (!$scope->isInClass()) { + if ($selfClassReflection === null) { return [ RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $className)) ->identifier(sprintf('outOfClass.%s', $lowercasedClassName)) ->build(), ]; } - $currentClassReflection = $scope->getClassReflection(); - if ($currentClassReflection->getParentClass() === null) { + if ($selfClassReflection->getParentClass() === null) { return [ RuleErrorBuilder::message(sprintf( 'Access to parent::%s but %s does not extend any class.', $constantName, - $currentClassReflection->getDisplayName(), + $selfClassReflection->getDisplayName(), ))->identifier('class.noParent')->build(), ]; } diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index f9c19257b04..8d93876ba16 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -27,6 +27,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; +use PHPStan\Rules\SelfClassResolver; use PHPStan\ShouldNotHappenException; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; @@ -40,6 +41,7 @@ use function array_map; use function array_merge; use function count; +use function in_array; use function sprintf; use function strtolower; @@ -130,8 +132,15 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope&N $lowercasedClass = strtolower($class); $messages = []; $isStatic = false; + $isSelfClass = in_array($lowercasedClass, ['self', 'static', 'parent'], true); + if ($isSelfClass && SelfClassResolver::isAmbiguous($scope)) { + return []; + } + $selfClassReflection = $isSelfClass + ? SelfClassResolver::resolve($scope, $this->reflectionProvider) + : null; if ($lowercasedClass === 'static') { - if (!$scope->isInClass()) { + if ($selfClassReflection === null) { return [ RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $class)) ->identifier('outOfClass.static') @@ -140,7 +149,7 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope&N } $isStatic = true; - $classReflection = $scope->getClassReflection(); + $classReflection = $selfClassReflection; if (!$classReflection->isFinal()) { if (!$classReflection->hasConstructor()) { return []; @@ -158,33 +167,39 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope&N } } } elseif ($lowercasedClass === 'self') { - if (!$scope->isInClass()) { + if ($selfClassReflection === null) { return [ RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $class)) ->identifier('outOfClass.self') ->build(), ]; } - $classReflection = $scope->getClassReflection(); + $classReflection = $selfClassReflection; } elseif ($lowercasedClass === 'parent') { - if (!$scope->isInClass()) { + if ($selfClassReflection === null) { return [ RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $class)) ->identifier('outOfClass.parent') ->build(), ]; } - if ($scope->getClassReflection()->getParentClass() === null) { + $parentClassReflection = $selfClassReflection->getParentClass(); + if ($parentClassReflection === null) { + // a closure bound to another class has no method of that class to name + $isEnclosingClass = $scope->isInClass() && $scope->getClassReflection()->getName() === $selfClassReflection->getName(); return [ - RuleErrorBuilder::message(sprintf( + RuleErrorBuilder::message($isEnclosingClass ? sprintf( '%s::%s() calls new parent but %s does not extend any class.', - $scope->getClassReflection()->getDisplayName(), + $selfClassReflection->getDisplayName(), $scope->getFunctionName(), - $scope->getClassReflection()->getDisplayName(), + $selfClassReflection->getDisplayName(), + ) : sprintf( + 'Using new parent but %s does not extend any class.', + $selfClassReflection->getDisplayName(), ))->identifier('class.noParent')->build(), ]; } - $classReflection = $scope->getClassReflection()->getParentClass(); + $classReflection = $parentClassReflection; } else { if (!$this->reflectionProvider->hasClass($class)) { if ($scope->isInClassExists($class)) { diff --git a/src/Rules/Methods/StaticMethodCallCheck.php b/src/Rules/Methods/StaticMethodCallCheck.php index aaabe3d1e79..7c368124845 100644 --- a/src/Rules/Methods/StaticMethodCallCheck.php +++ b/src/Rules/Methods/StaticMethodCallCheck.php @@ -22,10 +22,12 @@ use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; +use PHPStan\Rules\SelfClassResolver; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\ErrorType; use PHPStan\Type\Generic\GenericClassStringType; +use PHPStan\Type\ObjectType; use PHPStan\Type\StaticType; use PHPStan\Type\StringType; use PHPStan\Type\Type; @@ -75,8 +77,12 @@ public function check( $className = (string) $class; $lowercasedClassName = strtolower($className); + if (in_array($lowercasedClassName, ['self', 'static', 'parent'], true) && SelfClassResolver::isAmbiguous($scope)) { + return [[], null]; + } + $selfClassReflection = SelfClassResolver::resolve($scope, $this->reflectionProvider); if (in_array($lowercasedClassName, ['self', 'static'], true)) { - if (!$scope->isInClass()) { + if ($selfClassReflection === null) { return [ [ RuleErrorBuilder::message(sprintf( @@ -92,7 +98,7 @@ public function check( } $classType = $scope->resolveTypeByName($class); } elseif ($lowercasedClassName === 'parent') { - if (!$scope->isInClass()) { + if ($selfClassReflection === null) { return [ [ RuleErrorBuilder::message(sprintf( @@ -107,16 +113,21 @@ public function check( null, ]; } - $currentClassReflection = $scope->getClassReflection(); - if ($currentClassReflection->getParentClass() === null) { + // a closure bound to another class has no method of that class to name + $isEnclosingClass = $scope->isInClass() && $scope->getClassReflection()->getName() === $selfClassReflection->getName(); + if ($selfClassReflection->getParentClass() === null) { return [ [ - RuleErrorBuilder::message(sprintf( + RuleErrorBuilder::message($isEnclosingClass ? sprintf( '%s::%s() calls parent::%s() but %s does not extend any class.', - $scope->getClassReflection()->getDisplayName(), + $selfClassReflection->getDisplayName(), $scope->getFunctionName(), $methodName, - $scope->getClassReflection()->getDisplayName(), + $selfClassReflection->getDisplayName(), + ) : sprintf( + 'Calling parent::%s() but %s does not extend any class.', + $methodName, + $selfClassReflection->getDisplayName(), )) ->line($astName->getStartLine()) ->identifier('class.noParent') @@ -126,7 +137,7 @@ public function check( ]; } - if ($scope->getFunctionName() === null) { + if ($isEnclosingClass && $scope->getFunctionName() === null) { throw new ShouldNotHappenException(); } @@ -271,24 +282,28 @@ public function check( || $function->isStatic() || $scopeIsInMethodClassOrSubClass->no() ) { - // per php-src docs, this method can be called statically, even if declared non-static - if (strtolower($method->getName()) === 'loadhtml' && $method->getDeclaringClass()->getName() === DOMDocument::class) { - return [[], null]; - } + // a closure bound with a $this of the method's class calls its instance + // methods through self::/static:: as well + if (!self::isCalledOnBoundThis($scope, $classType)) { + // per php-src docs, this method can be called statically, even if declared non-static + if (strtolower($method->getName()) === 'loadhtml' && $method->getDeclaringClass()->getName() === DOMDocument::class) { + return [[], null]; + } - return [ - array_merge($errors, [ - RuleErrorBuilder::message(sprintf( - 'Static call to instance method %s::%s().', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - )) - ->line($astName->getStartLine()) - ->identifier('method.staticCall') - ->build(), - ]), - $method, - ]; + return [ + array_merge($errors, [ + RuleErrorBuilder::message(sprintf( + 'Static call to instance method %s::%s().', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + )) + ->line($astName->getStartLine()) + ->identifier('method.staticCall') + ->build(), + ]), + $method, + ]; + } } } @@ -349,4 +364,26 @@ public function check( return [$errors, $method]; } + /** + * Whether the scope is a closure bound with a `$this` that is certainly an instance of + * the classes of the static call. An undefined `$this` reads as an error type and a + * possibly defined one as mixed, neither of which is certainly such an instance. + */ + private static function isCalledOnBoundThis(Scope $scope, Type $classType): bool + { + $objectClassNames = $classType->getObjectClassNames(); + if ($objectClassNames === [] || !$scope->isInClosureBind()) { + return false; + } + + $boundThisType = $scope->getType(new Expr\Variable('this')); + foreach ($objectClassNames as $objectClassName) { + if (!(new ObjectType($objectClassName))->isSuperTypeOf($boundThisType)->yes()) { + return false; + } + } + + return true; + } + } diff --git a/src/Rules/Properties/AccessStaticPropertiesCheck.php b/src/Rules/Properties/AccessStaticPropertiesCheck.php index 92742621326..8e5f33ffd93 100644 --- a/src/Rules/Properties/AccessStaticPropertiesCheck.php +++ b/src/Rules/Properties/AccessStaticPropertiesCheck.php @@ -25,6 +25,7 @@ use PHPStan\Rules\NonStringableDynamicAccessCheck; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; +use PHPStan\Rules\SelfClassResolver; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; use PHPStan\Type\StringType; @@ -94,8 +95,12 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, if ($node->class instanceof Name) { $class = (string) $node->class; $lowercasedClass = strtolower($class); + if (in_array($lowercasedClass, ['self', 'static', 'parent'], true) && SelfClassResolver::isAmbiguous($scope)) { + return []; + } + $selfClassReflection = SelfClassResolver::resolve($scope, $this->reflectionProvider); if (in_array($lowercasedClass, ['self', 'static'], true)) { - if (!$scope->isInClass()) { + if ($selfClassReflection === null) { return [ RuleErrorBuilder::message(sprintf( 'Accessing %s::$%s outside of class scope.', @@ -109,7 +114,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, } $classType = $scope->resolveTypeByName($node->class); } elseif ($lowercasedClass === 'parent') { - if (!$scope->isInClass()) { + if ($selfClassReflection === null) { return [ RuleErrorBuilder::message(sprintf( 'Accessing %s::$%s outside of class scope.', @@ -121,14 +126,20 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, ->build(), ]; } - if ($scope->getClassReflection()->getParentClass() === null) { + if ($selfClassReflection->getParentClass() === null) { + // a closure bound to another class has no method of that class to name + $isEnclosingClass = $scope->isInClass() && $scope->getClassReflection()->getName() === $selfClassReflection->getName(); return [ - RuleErrorBuilder::message(sprintf( + RuleErrorBuilder::message($isEnclosingClass ? sprintf( '%s::%s() accesses parent::$%s but %s does not extend any class.', - $scope->getClassReflection()->getDisplayName(), + $selfClassReflection->getDisplayName(), $scope->getFunctionName(), $name, - $scope->getClassReflection()->getDisplayName(), + $selfClassReflection->getDisplayName(), + ) : sprintf( + 'Accessing parent::$%s but %s does not extend any class.', + $name, + $selfClassReflection->getDisplayName(), )) ->line($node->name->getStartLine()) ->identifier('class.noParent') diff --git a/src/Rules/SelfClassResolver.php b/src/Rules/SelfClassResolver.php new file mode 100644 index 00000000000..4078f63f55e --- /dev/null +++ b/src/Rules/SelfClassResolver.php @@ -0,0 +1,53 @@ +isClosureBindScopeClassAmbiguous(); + } + + /** + * Null when there is no such class (outside a class, and not inside a closure bound to + * a known class). + */ + public static function resolve(Scope $scope, ReflectionProvider $reflectionProvider): ?ClassReflection + { + if ($scope->isInClosureBind()) { + // Scope::resolveName() follows the bound class, inside a class and outside one + $className = $scope->resolveName(new Name('self')); + if (strtolower($className) !== 'self' && $reflectionProvider->hasClass($className)) { + return $reflectionProvider->getClass($className); + } + } + + return $scope->isInClass() ? $scope->getClassReflection() : null; + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/closure-bind-named-arguments.php b/tests/PHPStan/Analyser/nsrt/closure-bind-named-arguments.php new file mode 100644 index 00000000000..a67896a8c60 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/closure-bind-named-arguments.php @@ -0,0 +1,85 @@ += 8.0 + +declare(strict_types = 1); + +namespace ClosureBindNamedArguments; + +use Closure; +use function PHPStan\Testing\assertNativeType; +use function PHPStan\Testing\assertType; + +class Target +{ + + private int $priv = 1; + +} + +function (Target $target): void { + // $newThis only + Closure::bind(function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + }, $target); + Closure::bind(closure: function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + }, newThis: $target); + Closure::bind(newThis: $target, closure: function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + }); + Closure::bind(function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + }, newThis: $target); + + // $newThis and $newScope in every order + Closure::bind(function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + assertType('int', $this->priv); + }, $target, Target::class); + Closure::bind(closure: function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + assertType('int', $this->priv); + }, newThis: $target, newScope: Target::class); + Closure::bind(closure: function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + assertType('int', $this->priv); + }, newScope: Target::class, newThis: $target); + Closure::bind(newThis: $target, closure: function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + assertType('int', $this->priv); + }, newScope: Target::class); + Closure::bind(newThis: $target, newScope: Target::class, closure: function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + assertType('int', $this->priv); + }); + Closure::bind(newScope: Target::class, closure: function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + assertType('int', $this->priv); + }, newThis: $target); + Closure::bind(newScope: Target::class, newThis: $target, closure: function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + assertType('int', $this->priv); + }); + + // positional and named arguments mixed + Closure::bind(function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + assertType('int', $this->priv); + }, $target, newScope: Target::class); + Closure::bind(function () { + assertType('ClosureBindNamedArguments\Target', $this); + assertNativeType('ClosureBindNamedArguments\Target', $this); + assertType('int', $this->priv); + }, newScope: Target::class, newThis: $target); +}; diff --git a/tests/PHPStan/Analyser/nsrt/closure-bind-scope-ambiguous.php b/tests/PHPStan/Analyser/nsrt/closure-bind-scope-ambiguous.php new file mode 100644 index 00000000000..0deda68bd9e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/closure-bind-scope-ambiguous.php @@ -0,0 +1,114 @@ += 8.1 + +declare(strict_types = 1); + +namespace ClosureBindScopeAmbiguous; + +use Closure; +use function PHPStan\Testing\assertType; + +class Base +{ + + public static function make(): static + { + return new static(); // @phpstan-ignore new.static + } + + public static function base(): self + { + return new self(); + } + +} + +class Foo extends Base +{ + + public const A = 'Foo'; + +} + +class Bar extends Base +{ + + public const A = 'Bar'; + +} + +enum Suit: string +{ + + case Hearts = 'H'; + +} + +/** + * @param class-string|class-string $withAncestor + * @param class-string|class-string $noAncestor + * @param class-string $plain + */ +function doFoo(string $withAncestor, string $noAncestor, string $plain, string $str): void +{ + // bound to one of several classes: self/static is the closest class they all extend + assertType('static(ClosureBindScopeAmbiguous\Base)', Closure::bind(static fn () => static::make(), null, $withAncestor)()); + assertType('ClosureBindScopeAmbiguous\Base', Closure::bind(static fn () => self::make(), null, $withAncestor)()); + assertType('ClosureBindScopeAmbiguous\Base', Closure::bind(static fn () => self::base(), null, $withAncestor)()); + + // ...and without one, or when the class is unknown, nothing is known about it + assertType('*ERROR*', Closure::bind(static fn () => self::A, null, $withAncestor)()); + assertType('*ERROR*', Closure::bind(static fn () => self::Hearts, null, $noAncestor)()); + assertType('*ERROR*', Closure::bind(static fn () => self::A, null, $plain)()); + assertType('*ERROR*', Closure::bind(static fn () => self::A, null, $str)()); // @phpstan-ignore argument.type + + // new and ::class name the closest class all candidates extend, or some class + assertType('array{ClosureBindScopeAmbiguous\\Base, static(ClosureBindScopeAmbiguous\\Base), ClosureBindScopeAmbiguous\\Base}', Closure::bind(static fn () => [new self(), new static(), new parent()], null, $withAncestor)()); + assertType('array{class-string, class-string, class-string}', Closure::bind(static fn () => [self::class, static::class, parent::class], null, $withAncestor)()); + assertType('array{object, object}', Closure::bind(static fn () => [new self(), new static()], null, $noAncestor)()); + assertType('array{class-string, class-string, class-string}', Closure::bind(static fn () => [self::class, static::class, parent::class], null, $noAncestor)()); + assertType('array{object, class-string}', Closure::bind(static fn () => [new self(), self::class], null, $plain)()); + + // so do the type hints of the bound closure + assertType('static-Closure(ClosureBindScopeAmbiguous\\Base, ClosureBindScopeAmbiguous\\Base): ClosureBindScopeAmbiguous\\Base', Closure::bind(static fn (self $x, parent $y): self => $x, null, $withAncestor)); + assertType('static-Closure(mixed): mixed', Closure::bind(static fn (self $x): parent => $x, null, $plain)); + assertType('static-Closure(mixed): mixed', Closure::bind(static fn (self $x): self => $x, null, $noAncestor)); + assertType('static-Closure(ClosureBindScopeAmbiguous\\Foo, ClosureBindScopeAmbiguous\\Base): ClosureBindScopeAmbiguous\\Base', Closure::bind(static fn (self $x, parent $y): parent => $y, null, Foo::class)); +} + +/** + * @param-closure-this object $closure + */ +function withObjectThis(Closure $closure): void +{ +} + +// a closure bound to an object of no known class: self names some class +withObjectThis(function (): void { + assertType('class-string', self::class); + assertType('object', new self()); +}); + +function closureCall(object $object): void +{ + assertType('class-string', (fn () => self::class)->call($object)); +} + +class Container extends Base +{ + + public const OWN = 'own'; + + /** + * @param class-string|class-string $withAncestor + * @param class-string|class-string $noAncestor + * @param class-string $plain + */ + public function doFoo(string $withAncestor, string $noAncestor, string $plain): void + { + // inside a class an ambiguous bound class does not fall back to the enclosing one + assertType('array{ClosureBindScopeAmbiguous\\Base, class-string, ClosureBindScopeAmbiguous\\Base}', Closure::bind(static fn () => [new self(), self::class, self::make()], null, $withAncestor)()); + assertType('array{object, class-string}', Closure::bind(static fn () => [new self(), self::class], null, $noAncestor)()); + assertType('array{object, class-string, *ERROR*}', Closure::bind(static fn () => [new self(), self::class, self::OWN], null, $plain)()); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/closure-bind-scope-named-arguments.php b/tests/PHPStan/Analyser/nsrt/closure-bind-scope-named-arguments.php new file mode 100644 index 00000000000..5ec465e5191 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/closure-bind-scope-named-arguments.php @@ -0,0 +1,30 @@ += 8.0 + +declare(strict_types = 1); + +namespace ClosureBindScopeNamedArguments; + +use Closure; +use function PHPStan\Testing\assertType; + +class Foo +{ + + protected const A = 'Foo'; + +} + +final class Bar extends Foo +{ + + protected const A = 'Bar'; + +} + +// The bind scope is resolved by argument name, so any order works. +assertType("'Foo'", Closure::bind(closure: static fn () => self::A, newThis: null, newScope: Foo::class)()); +assertType("'Foo'", Closure::bind(closure: static fn () => self::A, newScope: Foo::class, newThis: null)()); +assertType("'Bar'", Closure::bind(newScope: Bar::class, closure: static fn () => self::A, newThis: null)()); + +// Positional and named arguments mixed together. +assertType("'Foo'", Closure::bind(static fn () => parent::A, null, newScope: Bar::class)()); diff --git a/tests/PHPStan/Analyser/nsrt/closure-bind-scope-param-closure-this.php b/tests/PHPStan/Analyser/nsrt/closure-bind-scope-param-closure-this.php new file mode 100644 index 00000000000..8b73ffc18e1 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/closure-bind-scope-param-closure-this.php @@ -0,0 +1,28 @@ + Foo::A, null, Foo::class)()); +assertType("'Bar'", Closure::bind(static fn () => Bar::A, null, Bar::class)()); +assertType("'Foo'", Closure::bind(static fn () => self::A, null, Foo::class)()); +assertType("'Bar'", Closure::bind(static fn () => self::A, null, Bar::class)()); +assertType("'Foo'", Closure::bind(static fn () => parent::A, null, Bar::class)()); + +// ::class magic constant. +assertType("'ClosureBindScope\\\\Foo'", Closure::bind(static fn () => self::class, null, Foo::class)()); +assertType("'ClosureBindScope\\\\Foo'", Closure::bind(static fn () => parent::class, null, Bar::class)()); + +// Static method calls. +assertType("'Foo'", Closure::bind(static fn () => self::staticMethod(), null, Foo::class)()); +assertType("'Bar'", Closure::bind(static fn () => self::staticMethod(), null, Bar::class)()); +assertType("'Foo'", Closure::bind(static fn () => parent::staticMethod(), null, Bar::class)()); + +// Static property access. +assertType('int', Closure::bind(static fn () => self::$staticProp, null, Foo::class)()); + +// Instantiation via self/parent/static. +assertType('ClosureBindScope\Foo', Closure::bind(static fn () => new self(), null, Foo::class)()); +assertType('ClosureBindScope\Foo', Closure::bind(static fn () => new parent(), null, Bar::class)()); + +// static:: follows the bound class like self:: does. +assertType("'Bar'", Closure::bind(static fn () => static::A, null, Bar::class)()); +assertType('static(ClosureBindScope\\Foo)', Closure::bind(static fn () => new static(), null, Foo::class)()); + +// A closure nested in a bound closure is bound too... +assertType("'Bar'", Closure::bind(static fn () => (static fn () => self::A)(), null, Bar::class)()); +assertType("'BarFoo'", Closure::bind(static fn () => Closure::bind(static fn () => self::A, null, Bar::class)() . self::A, null, Foo::class)()); + +// ...but a class declared in one has its own self/static. +Closure::bind(static fn () => new class { + + public const Z = 'z'; + + public function f(): void + { + assertType("'z'", self::Z); + assertType("'z'", static::Z); + } + +}, null, Foo::class); +Closure::bind(static fn () => new class extends Foo { + + public function f(): void + { + assertType("'Foo'", self::A); + assertType("'Foo'", parent::A); + } + +}, null, Bar::class); + +// The bound class is the one the call site evaluated, so an object newScope works too. +function objectScope(Foo $foo, Bar $bar): void +{ + assertType('ClosureBindScope\Foo', Closure::bind(static fn () => new self(), null, $foo)()); + assertType('static(ClosureBindScope\Foo)', Closure::bind(static fn () => new static(), null, $foo)()); + assertType('ClosureBindScope\Foo', Closure::bind(static fn () => new parent(), null, $bar)()); + assertType("'Foo'", Closure::bind(static fn () => self::staticMethod(), null, $foo)()); + assertType("'Bar'", Closure::bind(static fn () => self::staticMethod(), null, $bar)()); + assertType("'Foo'", Closure::bind(static fn () => self::A, null, $foo)()); + assertType("'Foo'", Closure::bind(static fn () => parent::A, null, $bar)()); +} + +// The newScope argument is evaluated where the call is, not inside the closure body. +/** @param class-string $cls */ +function variableScope(string $cls): void +{ + assertType("'Bar'", Closure::bind(static function () { + return self::A; + }, null, $cls)()); + assertType("'Bar'", Closure::bind(static function () use ($cls) { + $cls = Foo::class; + return self::A; + }, null, $cls)()); +} + +class Container +{ + + public function doBindFromInsideClass(): void + { + // Even when Closure::bind() is called from inside another class, the bound + // scope wins over the enclosing class. + assertType("'Foo'", Closure::bind(static fn () => self::A, null, Foo::class)()); + assertType("'Bar'", Closure::bind(static fn () => self::A, null, Bar::class)()); + assertType('ClosureBindScope\Foo', Closure::bind(static fn () => new self(), null, Foo::class)()); + // the default 'static' scope keeps the enclosing class + assertType("'ClosureBindScope\\\\Container'", Closure::bind(static fn () => self::class, null, 'static')()); + assertType("'ClosureBindScope\\\\Container'", Closure::bind(static fn () => self::class, null)()); + // the arguments are evaluated in the enclosing class + assertType("'ClosureBindScope\\\\Container'", Closure::bind(static fn () => self::class, null, self::class)()); + // parent is the bound class's parent, not the enclosing class's + assertType("'Foo'", Closure::bind(static fn () => parent::staticMethod(), null, Bar::class)()); + assertType('ClosureBindScope\Foo', Closure::bind(static fn () => new parent(), null, Bar::class)()); + } + +} diff --git a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php index e6e6d1837bf..acbd34e7af1 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php @@ -184,6 +184,17 @@ public function testClassConstantVisibility(): void ]); } + #[RequiresPhp('>= 8.0.0')] + public function testClosureBindNamedArguments(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-named-arguments.php'], [ + [ + 'Access to protected constant C of class ClosureBindNamedArgumentsConstant\\Target.', + 53, + ], + ]); + } + public function testClassExists(): void { $this->analyse([__DIR__ . '/data/class-exists.php'], [ @@ -636,4 +647,84 @@ public function testClosureAttributeArguments(): void ]); } + #[RequiresPhp('>= 8.0.0')] + public function testClosureBindScopeNamedArguments(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope-named-arguments.php'], []); + } + + public function testClosureBindScope(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope.php'], [ + [ + 'Access to undefined constant ClosureBindScopeClasses\\Bar::P.', + 47, + ], + [ + 'Access to parent::X but ClosureBindScopeClasses\\NoParent does not extend any class.', + 50, + ], + [ + 'Access to undefined constant ClosureBindScopeClasses\\Foo::NOPE.', + 53, + ], + [ + 'Access to undefined constant static(ClosureBindScopeClasses\\Foo)::NOPE.', + 54, + ], + [ + 'Using self outside of class scope.', + 57, + ], + [ + 'Using parent outside of class scope.', + 57, + ], + [ + 'Using static outside of class scope.', + 57, + ], + [ + 'Using self outside of class scope.', + 58, + ], + [ + 'Access to undefined constant ClosureBindScopeClasses\\Foo::OWN.', + 70, + ], + [ + 'Access to parent::X but ClosureBindScopeClasses\\NoParent does not extend any class.', + 75, + ], + ]); + } + + public function testClosureBindScopeNestedClass(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope-nested-class.php'], [ + [ + 'Using self outside of class scope.', + 48, + ], + ]); + } + + public function testClosureBindScopeAmbiguous(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope-ambiguous.php'], [ + [ + 'Using self outside of class scope.', + 49, + ], + [ + 'Using self outside of class scope.', + 50, + ], + [ + 'Using self outside of class scope.', + 95, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 5bf9468dc76..91d6c4f252d 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -824,4 +824,57 @@ public function testBug14002Part2(): void $this->analyse([__DIR__ . '/data/bug-14002-2.php'], []); } + public function testClosureBindScope(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope.php'], [ + [ + 'Using new parent but ClosureBindScopeClasses\\NoParent does not extend any class.', + 50, + ], + [ + 'Using self outside of class scope.', + 57, + ], + [ + 'Using parent outside of class scope.', + 57, + ], + [ + 'Using static outside of class scope.', + 57, + ], + [ + 'Using self outside of class scope.', + 58, + ], + [ + 'Using new parent but ClosureBindScopeClasses\\NoParent does not extend any class.', + 75, + ], + ]); + } + + public function testClosureBindScopeNestedClass(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope-nested-class.php'], []); + } + + public function testClosureBindScopeAmbiguous(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope-ambiguous.php'], [ + [ + 'Using self outside of class scope.', + 49, + ], + [ + 'Using self outside of class scope.', + 50, + ], + [ + 'Using self outside of class scope.', + 95, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/closure-bind-named-arguments.php b/tests/PHPStan/Rules/Classes/data/closure-bind-named-arguments.php new file mode 100644 index 00000000000..87d033de9f2 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/closure-bind-named-arguments.php @@ -0,0 +1,54 @@ += 8.0 + +declare(strict_types = 1); + +namespace ClosureBindNamedArgumentsConstant; + +use Closure; + +class Target +{ + + protected const C = 'c'; + +} + +class Other +{ + +} + +// Every form binds the scope to Target, so accessing its protected constant +// is allowed no matter how the arguments are written. +Closure::bind(function () { + return Target::C; +}, null, Target::class); +Closure::bind(closure: function () { + return Target::C; +}, newThis: null, newScope: Target::class); +Closure::bind(closure: function () { + return Target::C; +}, newScope: Target::class, newThis: null); +Closure::bind(newThis: null, closure: function () { + return Target::C; +}, newScope: Target::class); +Closure::bind(newThis: null, newScope: Target::class, closure: function () { + return Target::C; +}); +Closure::bind(newScope: Target::class, closure: function () { + return Target::C; +}, newThis: null); +Closure::bind(newScope: Target::class, newThis: null, closure: function () { + return Target::C; +}); +Closure::bind(function () { + return Target::C; +}, null, newScope: Target::class); +Closure::bind(function () { + return Target::C; +}, newScope: Target::class, newThis: null); + +// Bound to another scope, the constant stays inaccessible. +Closure::bind(newScope: Other::class, closure: function () { + return Target::C; +}, newThis: new Target()); diff --git a/tests/PHPStan/Rules/Classes/data/closure-bind-scope-ambiguous.php b/tests/PHPStan/Rules/Classes/data/closure-bind-scope-ambiguous.php new file mode 100644 index 00000000000..09681645b25 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/closure-bind-scope-ambiguous.php @@ -0,0 +1,96 @@ += 8.1 + +declare(strict_types = 1); + +namespace ClosureBindScopeAmbiguousClasses; + +use Closure; + +class Base +{ + +} + +class Foo extends Base +{ + + protected const A = 'Foo'; + +} + +class Bar extends Base +{ + + protected const A = 'Bar'; + +} + +enum Suit: string +{ + + case Hearts = 'H'; + +} + +/** + * @param class-string|class-string $withAncestor + * @param class-string|class-string $noAncestor + * @param class-string $plain + */ +function doFoo(string $withAncestor, string $noAncestor, string $plain, ?string $nullable): void +{ + // bound to a class that is not exactly one known class: nothing to check + Closure::bind(static fn () => [self::A, static::A, parent::A, new self(), new static(), new parent()], null, $withAncestor); + Closure::bind(static fn () => [self::Hearts, self::A, new self()], null, $noAncestor); + Closure::bind(static fn () => [self::A, parent::A, new self(), new parent()], null, $plain); + Closure::bind(static fn () => [self::A, new self()], null, $nullable); + + // 'static' and null bind to no class here + Closure::bind(static fn () => [self::A, new self()], null, 'static'); + Closure::bind(static fn () => [self::A, new self()], null, null); +} + +class Container +{ + + /** + * @param class-string|class-string $withAncestor + * @param class-string $plain + */ + public function run(string $withAncestor, string $plain): void + { + Closure::bind(static fn () => [self::NOPE, new self(), new parent()], null, $withAncestor); + Closure::bind(static fn () => [self::NOPE, new self(), new parent()], null, $plain); + } + +} + +class WithProtected +{ + + protected const PROT = 'p'; + + private const PRIV = 'p'; + + protected function __construct() + { + } + +} + +/** + * @param class-string $plain + * @param class-string|class-string $oneOfThem + * @param 'static'|null $staticOrNull + */ +function doBar(string $plain, string $oneOfThem, ?string $staticOrNull): void +{ + // an unknown bound class may be the one declaring the members + Closure::bind(static fn () => [WithProtected::PROT, WithProtected::PRIV, new WithProtected()], null, $plain); + + // one of several classes: accessible from any of them + Closure::bind(static fn () => [WithProtected::PROT, WithProtected::PRIV, new WithProtected()], null, $oneOfThem); + + // 'static' or null binds to no class here + Closure::bind(static fn () => [self::A, new self()], null, $staticOrNull); +} diff --git a/tests/PHPStan/Rules/Classes/data/closure-bind-scope-named-arguments.php b/tests/PHPStan/Rules/Classes/data/closure-bind-scope-named-arguments.php new file mode 100644 index 00000000000..37c5ff8e485 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/closure-bind-scope-named-arguments.php @@ -0,0 +1,23 @@ += 8.0 + +declare(strict_types = 1); + +namespace ClosureBindScopeNamedArguments; + +use Closure; + +class Target +{ + + protected const C = 'c'; + +} + +// Every form binds the scope to Target, so self:: names it and its protected constant +// is accessible no matter how the arguments are written. +Closure::bind(static fn () => self::C, null, Target::class); +Closure::bind(closure: static fn () => self::C, newThis: null, newScope: Target::class); +Closure::bind(closure: static fn () => self::C, newScope: Target::class, newThis: null); +Closure::bind(newScope: Target::class, closure: static fn () => self::C, newThis: null); +Closure::bind(static fn () => self::C, null, newScope: Target::class); +Closure::bind(static fn () => self::C, newScope: Target::class, newThis: null); diff --git a/tests/PHPStan/Rules/Classes/data/closure-bind-scope-nested-class.php b/tests/PHPStan/Rules/Classes/data/closure-bind-scope-nested-class.php new file mode 100644 index 00000000000..8dfcae670f3 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/closure-bind-scope-nested-class.php @@ -0,0 +1,75 @@ + [self::A, static::A, new self(), new static()], null, Foo::class); + Closure::bind(static fn () => [parent::A, new parent()], null, Bar::class); + + // an object newScope binds its class + Closure::bind(static fn () => [self::A, new self()], null, $foo); + Closure::bind(static fn () => [parent::A, new parent()], null, $bar); + + // private members of Foo are not accessible from the Bar scope + Closure::bind(static fn () => self::P, null, Bar::class); + + // the bound class has no parent + Closure::bind(static fn () => [parent::X, new parent()], null, NoParent::class); + + // undefined constant of the bound class + Closure::bind(static fn () => self::NOPE, null, Foo::class); + Closure::bind(static fn () => static::NOPE, null, Foo::class); + + // without a bound class self/parent/static stay outside of class scope + Closure::bind(static fn () => [self::A, new self(), parent::A, new parent(), static::A, new static()], null); + Closure::bind(static fn () => [self::A, new self()], null, 'static'); +}; + +class Container +{ + + private const OWN = 1; + + public function run(): void + { + // the bound class wins over the enclosing class + Closure::bind(static fn () => [self::A, new self(), parent::A, new parent()], null, Bar::class); + Closure::bind(static fn () => self::OWN, null, Foo::class); + // the arguments are evaluated in the enclosing class: self::class is Container + Closure::bind(static fn () => [self::OWN, new self()], null, self::class); + Closure::bind(static fn () => [self::OWN, new self()], null, static::class); + // a parent-less bound class in a class context + Closure::bind(static fn () => [parent::X, new parent()], null, NoParent::class); + } + +} diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php index ca7f16511e7..ff11f916d1c 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php @@ -350,4 +350,18 @@ public function testNoDiscardVoid(): void ]); } + public function testClosureBindScope(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope-typehints.php'], [ + [ + 'Parameter $x of anonymous function has invalid type self.', + 49, + ], + [ + 'Anonymous function has invalid return type self.', + 49, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php index 5fbefbb56d8..a901d3be1db 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php @@ -364,4 +364,18 @@ public function testNoDiscardVoid(): void ]); } + public function testClosureBindScope(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope-typehints.php'], [ + [ + 'Parameter $x of anonymous function has invalid type self.', + 50, + ], + [ + 'Anonymous function has invalid return type self.', + 50, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/closure-bind-scope-typehints.php b/tests/PHPStan/Rules/Functions/data/closure-bind-scope-typehints.php new file mode 100644 index 00000000000..2fa5b89bef9 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/closure-bind-scope-typehints.php @@ -0,0 +1,53 @@ +|class-string $withAncestor + * @param class-string|class-string $noAncestor + * @param class-string $plain + */ +function doFoo(string $withAncestor, string $noAncestor, string $plain): void +{ + // bound to one class: self/parent are its own and its parent + Closure::bind(static fn (self $x, parent $y): self => $x, null, Foo::class); + Closure::bind(static function (self $x, parent $y): parent { + return $y; + }, null, Foo::class); + + // bound to a class that is not exactly one known class + Closure::bind(static fn (self $x, parent $y): self => $x, null, $withAncestor); + Closure::bind(static fn (self $x): parent => $x, null, $noAncestor); + Closure::bind(static fn (self $x): parent => $x, null, $plain); + Closure::bind(static function (self $x): parent { + return $x; + }, null, $plain); + + // bound to no class + Closure::bind(static fn (self $x): self => $x, null, 'static'); + Closure::bind(static function (self $x): self { + return $x; + }, null); +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 8e6c4084026..2ba618e6a42 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -1111,6 +1111,20 @@ public function testBug14715(): void ]); } + #[RequiresPhp('>= 8.0.0')] + public function testClosureBindNamedArguments(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/closure-bind-named-arguments.php'], [ + [ + 'Call to protected method prot() of class ClosureBindNamedArgumentsMethod\\Target.', + 57, + ], + ]); + } + public function testArrowFunctionClosureBind(): void { $this->checkThisOnly = false; @@ -4616,4 +4630,13 @@ public function testDeadCatch(): void ]); } + #[RequiresPhp('>= 8.1.0')] + public function testClosureBindScopeAmbiguous(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/closure-bind-scope-ambiguous.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 33f7859302b..28bd1d8956c 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -847,6 +847,36 @@ public function testClosureBindParamClosureThis(): void ]); } + #[RequiresPhp('>= 8.0.0')] + public function testClosureBindParamClosureThisNamedArguments(): void + { + $this->checkThisOnly = false; + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/closure-bind-param-closure-this-named.php'], [ + [ + 'Parameter $newThis of static method Closure::bind() expects stdClass, ClosureBindParamClosureThisNamed\\Foo given.', + 19, + ], + [ + 'Parameter $newThis of static method Closure::bind() expects stdClass, ClosureBindParamClosureThisNamed\\Foo given.', + 20, + ], + [ + 'Parameter $newThis of static method Closure::bind() expects stdClass, ClosureBindParamClosureThisNamed\\Foo given.', + 21, + ], + [ + 'Parameter $newThis of static method Closure::bind() expects stdClass, ClosureBindParamClosureThisNamed\\Foo given.', + 22, + ], + [ + 'Parameter $newThis of static method Closure::bind() expects stdClass, ClosureBindParamClosureThisNamed\\Foo given.', + 23, + ], + ]); + } + public function testClosureBind(): void { $this->checkThisOnly = false; @@ -1156,4 +1186,70 @@ public function testBug11421(): void ]); } + #[RequiresPhp('>= 8.1.0')] + public function testClosureBindScope(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/closure-bind-scope.php'], [ + [ + 'Call to private static method psm() of class ClosureBindScopeMethods\\Foo.', + 53, + ], + [ + 'Calling parent::s() but ClosureBindScopeMethods\\NoParent does not extend any class.', + 56, + ], + [ + 'Call to an undefined static method ClosureBindScopeMethods\\Foo::nope().', + 59, + ], + [ + 'Calling self::sm() outside of class scope.', + 62, + ], + [ + 'Calling parent::sm() outside of class scope.', + 62, + ], + [ + 'Calling static::sm() outside of class scope.', + 62, + ], + [ + 'Call to an undefined static method ClosureBindScopeMethods\\Foo::own().', + 76, + ], + [ + 'Calling parent::s() but ClosureBindScopeMethods\\NoParent does not extend any class.', + 80, + ], + [ + 'Static call to instance method ClosureBindScopeMethods\\Foo::im().', + 89, + ], + [ + 'Static call to instance method ClosureBindScopeMethods\\Foo::im().', + 91, + ], + ]); + } + + public function testBug6319(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/bug-6319.php'], []); + } + + #[RequiresPhp('>= 8.1.0')] + public function testClosureBindScopeAmbiguous(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/closure-bind-scope-ambiguous.php'], [ + [ + 'Calling self::sm() outside of class scope.', + 39, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php b/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php index 6576eecfdd2..c277d3ce4dd 100644 --- a/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php +++ b/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php @@ -135,4 +135,46 @@ public function testConditionallyExecutedCode(): void $this->analyse([__DIR__ . '/data/static-method-callable-php-versions.php'], $errors); } + #[RequiresPhp('>= 8.1.0')] + public function testClosureBindScope(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope.php'], [ + [ + 'Call to private static method psm() of class ClosureBindScopeMethods\\Foo.', + 53, + ], + [ + 'Calling parent::s() but ClosureBindScopeMethods\\NoParent does not extend any class.', + 56, + ], + [ + 'Call to an undefined static method ClosureBindScopeMethods\\Foo::nope().', + 59, + ], + [ + 'Calling self::sm() outside of class scope.', + 62, + ], + [ + 'Static call to instance method ClosureBindScopeMethods\\Foo::im().', + 89, + ], + [ + 'Static call to instance method ClosureBindScopeMethods\\Foo::im().', + 91, + ], + ]); + } + + #[RequiresPhp('>= 8.1.0')] + public function testClosureBindScopeAmbiguous(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope-ambiguous.php'], [ + [ + 'Calling self::sm() outside of class scope.', + 39, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-6319.php b/tests/PHPStan/Rules/Methods/data/bug-6319.php new file mode 100644 index 00000000000..67c207f80f6 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-6319.php @@ -0,0 +1,22 @@ + 'LC' : fn () => 'not-LC', null, A::class)(); + } +} + +var_dump( + (new HelloWorld())->sayHello() +); diff --git a/tests/PHPStan/Rules/Methods/data/closure-bind-named-arguments.php b/tests/PHPStan/Rules/Methods/data/closure-bind-named-arguments.php new file mode 100644 index 00000000000..f429d180b1d --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/closure-bind-named-arguments.php @@ -0,0 +1,59 @@ += 8.0 + +declare(strict_types = 1); + +namespace ClosureBindNamedArgumentsMethod; + +use Closure; + +class Target +{ + + protected function prot(): int + { + return 1; + } + +} + +class Other +{ + +} + +function (Target $target): void { + // Every form binds $this to $target and the scope to Target, so calling + // its protected method is allowed no matter how the arguments are written. + Closure::bind(function () { + return $this->prot(); + }, $target, Target::class); + Closure::bind(closure: function () { + return $this->prot(); + }, newThis: $target, newScope: Target::class); + Closure::bind(closure: function () { + return $this->prot(); + }, newScope: Target::class, newThis: $target); + Closure::bind(newThis: $target, closure: function () { + return $this->prot(); + }, newScope: Target::class); + Closure::bind(newThis: $target, newScope: Target::class, closure: function () { + return $this->prot(); + }); + Closure::bind(newScope: Target::class, closure: function () { + return $this->prot(); + }, newThis: $target); + Closure::bind(newScope: Target::class, newThis: $target, closure: function () { + return $this->prot(); + }); + Closure::bind(function () { + return $this->prot(); + }, $target, newScope: Target::class); + Closure::bind(function () { + return $this->prot(); + }, newScope: Target::class, newThis: $target); + + // Bound to another scope, the method stays inaccessible. + Closure::bind(newScope: Other::class, closure: function () use ($target) { + return $target->prot(); + }, newThis: $target); +}; diff --git a/tests/PHPStan/Rules/Methods/data/closure-bind-param-closure-this-named.php b/tests/PHPStan/Rules/Methods/data/closure-bind-param-closure-this-named.php new file mode 100644 index 00000000000..800c4617e21 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/closure-bind-param-closure-this-named.php @@ -0,0 +1,40 @@ += 8.0 + +declare(strict_types = 1); + +namespace ClosureBindParamClosureThisNamed; + +use Closure; + +class Foo +{ + + /** + * @param-closure-this \stdClass $c + */ + public function doFoo(\Closure $c): void + { + Closure::bind(closure: $c, newThis: new \stdClass()); // ok + Closure::bind(newThis: new \stdClass(), closure: $c); // ok + Closure::bind(closure: $c, newThis: new self()); // error + Closure::bind(newThis: new self(), closure: $c); // error + Closure::bind($c, newThis: new self()); // error + Closure::bind(newScope: self::class, newThis: new self(), closure: $c); // error + Closure::bind(newThis: new self(), newScope: null, closure: $c); // error + } + + /** + * @param-closure-this \stdClass $c + */ + public function doBar(\Closure $c): void + { + // $c is the new $this here, not the bound closure + Closure::bind(newThis: $c, closure: function (): void { + + }); // ok + Closure::bind(newThis: $c, closure: function (): void { + + }, newScope: null); // ok + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/closure-bind-scope-ambiguous.php b/tests/PHPStan/Rules/Methods/data/closure-bind-scope-ambiguous.php new file mode 100644 index 00000000000..a6b3eb523e7 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/closure-bind-scope-ambiguous.php @@ -0,0 +1,64 @@ += 8.1 + +declare(strict_types = 1); + +namespace ClosureBindScopeAmbiguousMethods; + +use Closure; + +class Base +{ + +} + +class Foo extends Base +{ + + protected static function sm(): void + { + } + +} + +class Bar extends Base +{ + +} + +/** + * @param class-string|class-string $withAncestor + * @param class-string $plain + */ +function doFoo(string $withAncestor, string $plain): void +{ + // bound to a class that is not exactly one known class: nothing to check + Closure::bind(static fn () => [self::sm(), static::sm(), parent::sm(), self::sm(...)], null, $withAncestor); + Closure::bind(static fn () => [self::sm(), parent::sm(), self::sm(...)], null, $plain); + + // 'static' binds to no class here + Closure::bind(static fn () => [self::sm(), self::sm(...)], null, 'static'); +} + +class WithProtected +{ + + protected static function prot(): int + { + return 1; + } + + private function priv(): int + { + return 2; + } + +} + +/** + * @param class-string $plain + */ +function doBar(string $plain, WithProtected $object): void +{ + // an unknown bound class may be the one declaring the members + Closure::bind(static fn () => [WithProtected::prot(), $object->priv(), WithProtected::prot(...)], null, $plain); +} diff --git a/tests/PHPStan/Rules/Methods/data/closure-bind-scope.php b/tests/PHPStan/Rules/Methods/data/closure-bind-scope.php new file mode 100644 index 00000000000..315eddac501 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/closure-bind-scope.php @@ -0,0 +1,92 @@ += 8.1 + +declare(strict_types = 1); + +namespace ClosureBindScopeMethods; + +use Closure; + +class Foo +{ + + protected static function sm(): string + { + return 'Foo'; + } + + private static function psm(): string + { + return 'Foo'; + } + + public function im(): int + { + return 1; + } + +} + +final class Bar extends Foo +{ + +} + +class NoParent +{ + + public static function s(): void + { + } + +} + +function (Foo $foo, Bar $bar): void { + // the bound class's protected methods, through self/parent/static + Closure::bind(static fn () => [self::sm(), static::sm(), self::sm(...), static::sm(...)], null, Foo::class); + Closure::bind(static fn () => [parent::sm(), parent::sm(...)], null, Bar::class); + + // an object newScope binds its class + Closure::bind(static fn () => [self::sm(), self::sm(...)], null, $foo); + Closure::bind(static fn () => parent::sm(), null, $bar); + + // private methods of Foo are not accessible from the Bar scope + Closure::bind(static fn () => [self::psm(), self::psm(...)], null, Bar::class); + + // the bound class has no parent + Closure::bind(static fn () => [parent::s(), parent::s(...)], null, NoParent::class); + + // undefined method of the bound class + Closure::bind(static fn () => [self::nope(), self::nope(...)], null, Foo::class); + + // without a bound class self/parent/static stay outside of class scope + Closure::bind(static fn () => [self::sm(), parent::sm(), static::sm(), self::sm(...)], null); +}; + +class Container +{ + + private static function own(): void + { + } + + public function run(): void + { + // the bound class wins over the enclosing class + Closure::bind(static fn () => [self::sm(), parent::sm()], null, Bar::class); + Closure::bind(static fn () => self::own(), null, Foo::class); + // the arguments are evaluated in the enclosing class: self::class is Container + Closure::bind(static fn () => self::own(), null, self::class); + // a parent-less bound class in a class context + Closure::bind(static fn () => parent::s(), null, NoParent::class); + } + +} + +function (): void { + // a closure bound with a $this of the method's class calls its instance methods through self:: + Closure::bind(fn () => [self::im(), static::im(), self::im(...)], new Foo(), Foo::class); + Closure::bind(fn () => self::im(), new Bar(), Foo::class); + Closure::bind(static fn () => [self::im(), self::im(...)], null, Foo::class); + // ...but not one of a class the bound $this only may be an instance of + Closure::bind(fn () => [Bar::im(), Bar::im(...)], new Foo(), Foo::class); +}; diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 11329e9f8b5..0cdf23782ba 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -201,6 +201,20 @@ public function testAccessProperties(): void ); } + #[RequiresPhp('>= 8.0.0')] + public function testClosureBindNamedArguments(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->checkDynamicProperties = false; + $this->analyse([__DIR__ . '/data/closure-bind-named-arguments.php'], [ + [ + 'Access to private property ClosureBindNamedArgumentsProperty\\Target::$priv.', + 54, + ], + ]); + } + public function testAccessPropertiesWithoutUnionTypes(): void { $this->checkThisOnly = false; @@ -1321,4 +1335,12 @@ public function testBug15002(): void $this->analyse([__DIR__ . '/data/bug-15002.php'], []); } + public function testClosureBindScopeAmbiguous(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->checkDynamicProperties = false; + $this->analyse([__DIR__ . '/data/closure-bind-scope-ambiguous.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php index e9e8421ef6c..71d51fb221b 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php @@ -109,4 +109,27 @@ public function testAsymmetricVisibility(): void ]); } + public function testClosureBindScope(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope.php'], [ + [ + 'Access to private static property $pp of parent class ClosureBindScopeProperties\\Foo.', + 50, + ], + [ + 'Accessing parent::$y but ClosureBindScopeProperties\\NoParent does not extend any class.', + 56, + ], + [ + 'Accessing self::$sp outside of class scope.', + 65, + ], + ]); + } + + public function testClosureBindScopeAmbiguous(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope-ambiguous.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index b94650dcb48..cd8bb89fb0f 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -396,4 +396,52 @@ public function testBug15002(): void $this->analyse([__DIR__ . '/../Methods/data/bug-15002.php'], []); } + public function testClosureBindScope(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope.php'], [ + [ + 'Access to private static property $pp of parent class ClosureBindScopeProperties\\Foo.', + 48, + ], + [ + 'Accessing parent::$y but ClosureBindScopeProperties\\NoParent does not extend any class.', + 54, + ], + [ + 'Accessing parent::$y but ClosureBindScopeProperties\\NoParent does not extend any class.', + 56, + ], + [ + 'Access to an undefined static property ClosureBindScopeProperties\\Foo::$nope.', + 60, + ], + [ + 'Accessing self::$sp outside of class scope.', + 63, + ], + [ + 'Accessing parent::$sp outside of class scope.', + 63, + ], + [ + 'Accessing static::$sp outside of class scope.', + 63, + ], + [ + 'Accessing self::$sp outside of class scope.', + 65, + ], + ]); + } + + public function testClosureBindScopeAmbiguous(): void + { + $this->analyse([__DIR__ . '/data/closure-bind-scope-ambiguous.php'], [ + [ + 'Accessing self::$sp outside of class scope.', + 39, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/closure-bind-named-arguments.php b/tests/PHPStan/Rules/Properties/data/closure-bind-named-arguments.php new file mode 100644 index 00000000000..edc9b878f75 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/closure-bind-named-arguments.php @@ -0,0 +1,56 @@ += 8.0 + +declare(strict_types = 1); + +namespace ClosureBindNamedArgumentsProperty; + +use Closure; + +class Target +{ + + private int $priv = 1; + +} + +class Other +{ + +} + +function (Target $target): void { + // Every form binds $this to $target and the scope to Target, so reading + // its private property is allowed no matter how the arguments are written. + Closure::bind(function () { + return $this->priv; + }, $target, Target::class); + Closure::bind(closure: function () { + return $this->priv; + }, newThis: $target, newScope: Target::class); + Closure::bind(closure: function () { + return $this->priv; + }, newScope: Target::class, newThis: $target); + Closure::bind(newThis: $target, closure: function () { + return $this->priv; + }, newScope: Target::class); + Closure::bind(newThis: $target, newScope: Target::class, closure: function () { + return $this->priv; + }); + Closure::bind(newScope: Target::class, closure: function () { + return $this->priv; + }, newThis: $target); + Closure::bind(newScope: Target::class, newThis: $target, closure: function () { + return $this->priv; + }); + Closure::bind(function () { + return $this->priv; + }, $target, newScope: Target::class); + Closure::bind(function () { + return $this->priv; + }, newScope: Target::class, newThis: $target); + + // Bound to another scope, the property stays inaccessible. + Closure::bind(newScope: Other::class, closure: function () use ($target) { + return $target->priv; + }, newThis: $target); +}; diff --git a/tests/PHPStan/Rules/Properties/data/closure-bind-scope-ambiguous.php b/tests/PHPStan/Rules/Properties/data/closure-bind-scope-ambiguous.php new file mode 100644 index 00000000000..54eb1eef898 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/closure-bind-scope-ambiguous.php @@ -0,0 +1,67 @@ +|class-string $withAncestor + * @param class-string $plain + */ +function doFoo(string $withAncestor, string $plain): void +{ + // bound to a class that is not exactly one known class: nothing to check + Closure::bind(static fn () => [self::$sp, static::$sp, parent::$sp], null, $withAncestor); + Closure::bind(static function (): void { + self::$sp = 1; + parent::$sp = 2; + }, null, $plain); + + // 'static' binds to no class here + Closure::bind(static fn () => self::$sp, null, 'static'); +} + +class WithProtected +{ + + /** @var int */ + protected static $prot = 1; + + /** @var int */ + private static $priv = 2; + + protected int $instanceProt = 3; + +} + +/** + * @param class-string $plain + */ +function doBar(string $plain, WithProtected $object): void +{ + // an unknown bound class may be the one declaring the members + Closure::bind(static fn () => [WithProtected::$prot, WithProtected::$priv, $object->instanceProt], null, $plain); + Closure::bind(static function () use ($object): void { + WithProtected::$prot = 2; + WithProtected::$priv = 3; + $object->instanceProt = 4; + }, null, $plain); +} diff --git a/tests/PHPStan/Rules/Properties/data/closure-bind-scope.php b/tests/PHPStan/Rules/Properties/data/closure-bind-scope.php new file mode 100644 index 00000000000..a65c615ce67 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/closure-bind-scope.php @@ -0,0 +1,67 @@ + [self::$sp, static::$sp], null, Foo::class); + Closure::bind(static fn () => parent::$sp, null, Bar::class); + Closure::bind(static function (): void { + self::$sp = 2; + static::$sp = 3; + }, null, Foo::class); + Closure::bind(static function (): void { + parent::$sp = 2; + }, null, Bar::class); + + // an object newScope binds its class + Closure::bind(static fn () => self::$sp, null, $foo); + Closure::bind(static fn () => parent::$sp, null, $bar); + + // private properties of Foo are not accessible from the Bar scope + Closure::bind(static fn () => self::$pp, null, Bar::class); + Closure::bind(static function (): void { + self::$pp = 3; + }, null, Bar::class); + + // the bound class has no parent + Closure::bind(static fn () => parent::$y, null, NoParent::class); + Closure::bind(static function (): void { + parent::$y = 2; + }, null, NoParent::class); + + // undefined property of the bound class + Closure::bind(static fn () => self::$nope, null, Foo::class); + + // without a bound class self/parent/static stay outside of class scope + Closure::bind(static fn () => [self::$sp, parent::$sp, static::$sp], null); + Closure::bind(static function (): void { + self::$sp = 1; + }, null); +}; diff --git a/turbo-ext/src/ClassConstFetchHandler.cpp b/turbo-ext/src/ClassConstFetchHandler.cpp index d243cd5c052..412eb860c1a 100644 --- a/turbo-ext/src/ClassConstFetchHandler.cpp +++ b/turbo-ext/src/ClassConstFetchHandler.cpp @@ -6,7 +6,8 @@ * arginfo so Nette autowires it. processExpr() is registered as the class's * handler entry (Engine.h). The twin's closures are native closures capturing * what the PHP closures capture: the typeCallback ($this, $expr, $classResult, - * $classReflection) and the specifyTypesCallback ($this, $expr); the + * $classReflection, $ambiguousClassStringType) and the specifyTypesCallback + * ($this, $expr); the * class-type callback it hands to InitializerExprTypeResolver ($classResult, * $nativeTypesPromoted) is a pt_ietr_get_type over a stack capture array (the * resolver calls it synchronously). @@ -146,14 +147,25 @@ class ClassConstFetchHandler } // the enclosing class is lexical - fixed at this node - so resolve it - // once here instead of reading it off the callback's scope - zv::Val classReflection = zv::Val::null(); - bool inClass; - if (UNEXPECTED(!pt_mutating_scope_is_in_class(Z_OBJ_P(beforeScope), inClass))) return zv::Val(); - if (inClass) { - classReflection = pt_mutating_scope_get_class_reflection(Z_OBJ_P(beforeScope)); - if (UNEXPECTED(classReflection.isUndef())) return zv::Val(); + // once here instead of reading it off the callback's scope; inside a + // closure scoped by Closure::bind(), self/parent/static name the bound class + zv::Val classReflection = pt_mutating_scope_get_closure_bind_scope_class_reflection(Z_OBJ_P(beforeScope)); + if (UNEXPECTED(classReflection.isUndef())) return zv::Val(); + if (classReflection.isNull()) { + bool inClass; + if (UNEXPECTED(!pt_mutating_scope_is_in_class(Z_OBJ_P(beforeScope), inClass))) return zv::Val(); + if (inClass) { + bool ambiguous; + if (UNEXPECTED(!pt_mutating_scope_is_closure_bind_scope_class_ambiguous(Z_OBJ_P(beforeScope), ambiguous))) return zv::Val(); + if (!ambiguous) { + classReflection = pt_mutating_scope_get_class_reflection(Z_OBJ_P(beforeScope)); + if (UNEXPECTED(classReflection.isUndef())) return zv::Val(); + } + } } + // ...and their ::class is a class-string of the closest class all candidates extend + zv::Val ambiguousClassStringType = ambiguousClassString(beforeScope, expr); + if (UNEXPECTED(ambiguousClassStringType.isUndef())) return zv::Val(); zv::Val variableFlow; { @@ -171,7 +183,7 @@ class ClassConstFetchHandler variableFlow = pt_variable_flow_sequence(2, flows); if (UNEXPECTED(variableFlow.isUndef())) return zv::Val(); } - zv::Val typeCallback = pt_native_closure(&typeCallbackBody, self, expr, classResult.raw(), classReflection.raw()); + zv::Val typeCallback = pt_native_closure(&typeCallbackBody, self, expr, classResult.raw(), classReflection.raw(), ambiguousClassStringType.raw()); zv::Val specifyTypesCallback = pt_native_closure(&specifyTypesCallbackBody, self, expr); pt_expression_result_args args(scope.raw(), beforeScope, expr, hasYield, isAlwaysTerminating, throwPoints.raw(), impurePoints.raw(), typeCallback.raw(), specifyTypesCallback.raw()); @@ -280,6 +292,56 @@ class ClassConstFetchHandler return pt_dependencies_create_in(scope, types.raw(), classNames.raw()); } + /* the twin's $ambiguousClassStringType: for self/static/parent::class in + * a scope bound to a class that is not exactly one known class, a + * class-string of the closest class all candidates extend (or any + * class-string); IS_NULL otherwise, UNDEF = pending exception */ + static zv::Val ambiguousClassString(zval *scope, zval *expr) + { + zval *class_ = exprClass(expr); + if (UNEXPECTED(class_ == NULL)) return zv::Val(); + int classIsName = isInstanceOf(class_, PT_CLASS_NAME); + if (UNEXPECTED(classIsName < 0)) return zv::Val(); + if (!classIsName) return zv::Val::null(); + zv::Val lower = pt_name_node_to_lower_string(class_); + if (UNEXPECTED(lower.isUndef())) return zv::Val(); + if (!lower.ref().isString()) return zv::Val::null(); + zend_string *lowerClass = lower.ref().asString(); + bool isStatic = zend_string_equals_literal(lowerClass, "static"); + if (!isStatic && !zend_string_equals_literal(lowerClass, "self") && !zend_string_equals_literal(lowerClass, "parent")) return zv::Val::null(); + zval *name = exprName(expr); + if (UNEXPECTED(name == NULL)) return zv::Val(); + int nameIsIdentifier = isIdentifier(name); + if (UNEXPECTED(nameIsIdentifier < 0)) return zv::Val(); + if (!nameIsIdentifier) return zv::Val::null(); + zval *constantName = identifierName(name); + if (UNEXPECTED(constantName == NULL)) return zv::Val(); + if (Z_TYPE_P(constantName) != IS_STRING || !zend_string_equals_literal_ci(Z_STR_P(constantName), "class")) return zv::Val::null(); + bool ambiguous; + if (UNEXPECTED(!pt_mutating_scope_is_closure_bind_scope_class_ambiguous(Z_OBJ_P(scope), ambiguous))) return zv::Val(); + if (!ambiguous) return zv::Val::null(); + + zv::Val commonAncestor = pt_mutating_scope_get_closure_bind_scope_common_ancestor(Z_OBJ_P(scope), Z_OBJ_P(class_)); + if (UNEXPECTED(commonAncestor.isUndef())) return zv::Val(); + if (commonAncestor.isNull()) { + zval classString; + if (UNEXPECTED(!pt_class_string_type_new(&classString))) return zv::Val(); + return zv::Val::adopt(classString); + } + zv::Val objectType; + if (isStatic) { + zval staticType; + if (UNEXPECTED(!pt_static_type_new(&staticType, commonAncestor.raw()))) return zv::Val(); + objectType = zv::Val::adopt(staticType); + } else { + zv::Val ancestorName = pt_class_reflection_get_name(Z_OBJ_P(commonAncestor.raw())); + if (UNEXPECTED(ancestorName.isUndef())) return zv::Val(); + objectType = pt_type_new_object_type(ancestorName.raw()); + if (UNEXPECTED(objectType.isUndef())) return zv::Val(); + } + return pt_type_new_generic_class_string(objectType.raw()); + } + /* $constantReflection->getDeclaringClass()->getName(); UNDEF = pending * exception */ static zv::Val declaringClassNameOf(zval *constantReflection) @@ -290,8 +352,8 @@ class ClassConstFetchHandler } /* function (bool $nativeTypesPromoted) use ($expr, $classResult, - * $classReflection): Type — captures: $this, $expr, $classResult, - * $classReflection */ + * $classReflection, $ambiguousClassStringType): Type — captures: $this, + * $expr, $classResult, $classReflection, $ambiguousClassStringType */ static void typeCallbackBody(zval *captures, uint32_t argc, zval *argv, zval *return_value) { if (UNEXPECTED(!requireArguments(argc, 1, pt_ccfh_closure_name))) return; @@ -306,6 +368,10 @@ class ClassConstFetchHandler mixed.intoReturnValue(return_value); return; } + if (Z_TYPE(captures[4]) != IS_NULL) { + ZVAL_COPY(return_value, &captures[4]); + return; + } zval *class_ = exprClass(expr); if (UNEXPECTED(class_ == NULL)) return; diff --git a/turbo-ext/src/ClosureBindArgVisitor.cpp b/turbo-ext/src/ClosureBindArgVisitor.cpp index ef46e41e96f..c9e88959ac0 100644 --- a/turbo-ext/src/ClosureBindArgVisitor.cpp +++ b/turbo-ext/src/ClosureBindArgVisitor.cpp @@ -5,7 +5,7 @@ * original). * * Marks the closure argument of a Closure::bind() call that also passes a - * new $this. + * new $this, each argument found by position or by name. * * enterNode() always returns null, so the visitor is also registered with * pt_native_visitor_register(): the native NodeTraverser then runs it @@ -32,6 +32,72 @@ class ClosureBindArgVisitor public: /* enterNode(); the twin always returns null, false = pending exception */ [[nodiscard]] static bool enterNode(zend_object *visitor, zend_object *node) + { + zval *args = NULL; + if (!isClosureBindCall(node, &args)) return !EG(exception); + + BindArgs bindArgs; + findBindArgs(args, bindArgs); + if (bindArgs.closure != NULL && bindArgs.newThis != NULL) { + visitors::setAttributeTrue(bindArgs.closure, pt_closure_bind_arg_attribute_str); + } + return !EG(exception); + } + +private: + /* the $closureArg / $newThisArg the twin picks out of the call's + * arguments; NULL for null */ + struct BindArgs + { + zend_object *closure = NULL; + zend_object *newThis = NULL; + }; + + /* + * foreach ($node->getArgs() as $i => $arg): an unnamed argument by its + * position (0, 1), a named one by its name (closure, newThis); a named + * argument does not replace one already found (`??=`) + */ + static void findBindArgs(zval *args, BindArgs &out) + { + static NodeProp argNameProp = PT_NODE_PROP(PT_CLASS_ARG, "name"); + static NodeProp identifierProp = PT_IDENTIFIER_PROP; + + if (args == NULL || Z_TYPE_P(args) != IS_ARRAY) return; + for (auto entry : zv::ArrRef(args)) { + zv::Ref value = entry.value().deref(); + if (!value.isObject()) continue; + zend_object *arg = value.asObject(); + zend_object *name = visitors::isInstanceOf(arg, PT_CLASS_ARG) ? argNameProp.objectOf(arg, PT_CLASS_IDENTIFIER) : NULL; + if (name == NULL) { + if (entry.hasStringKey()) continue; + zend_ulong i = entry.indexKey(); + if (i == 0) { + out.closure = arg; + } else if (i == 1) { + out.newThis = arg; + } + continue; + } + + /* $arg->name->toString() */ + zend_string *argName = visitors::nameString(name, identifierProp); + if (argName == NULL) continue; + if (zend_string_equals_literal(argName, "closure")) { + if (out.closure == NULL) out.closure = arg; + } else if (zend_string_equals_literal(argName, "newThis")) { + if (out.newThis == NULL) out.newThis = arg; + } + } + } + + /* + * `$node instanceof StaticCall && $node->class instanceof Name && + * $node->class->toLowerString() === 'closure' && $node->name instanceof + * Identifier && $node->name->toLowerString() === 'bind' && + * !$node->isFirstClassCallable()`, plus the call's $args slot + */ + static bool isClosureBindCall(zend_object *node, zval **argsOut) { static NodeProp classProp = PT_NODE_PROP(PT_CLASS_STATIC_CALL, "class"); static NodeProp methodProp = PT_NODE_PROP(PT_CLASS_STATIC_CALL, "name"); @@ -39,23 +105,18 @@ class ClosureBindArgVisitor static NodeProp nameProp = PT_NAME_PROP; static NodeProp identifierProp = PT_IDENTIFIER_PROP; - if (!visitors::isInstanceOf(node, PT_CLASS_STATIC_CALL)) return !EG(exception); + if (!visitors::isInstanceOf(node, PT_CLASS_STATIC_CALL)) return false; zend_object *className = classProp.objectOf(node, PT_CLASS_NAME); - if (className == NULL) return !EG(exception); + if (className == NULL) return false; zend_string *classString = visitors::nameString(className, nameProp); - if (classString == NULL || !visitors::lowerEquals(classString, "closure")) return true; + if (classString == NULL || !visitors::lowerEquals(classString, "closure")) return false; zend_object *method = methodProp.objectOf(node, PT_CLASS_IDENTIFIER); - if (method == NULL) return !EG(exception); + if (method == NULL) return false; zend_string *methodName = visitors::nameString(method, identifierProp); - if (methodName == NULL || !visitors::lowerEquals(methodName, "bind")) return true; + if (methodName == NULL || !visitors::lowerEquals(methodName, "bind")) return false; zval *args = argsProp.of(node); - if (visitors::isFirstClassCallable(args)) return true; - - if (args == NULL || Z_TYPE_P(args) != IS_ARRAY || zend_hash_num_elements(Z_ARRVAL_P(args)) <= 1) return true; - zend_object *arg = visitors::argAt(args, 0); - if (arg != NULL) { - visitors::setAttributeTrue(arg, pt_closure_bind_arg_attribute_str); - } + if (visitors::isFirstClassCallable(args)) return false; + *argsOut = args; return true; } }; diff --git a/turbo-ext/src/MutatingScope.cpp b/turbo-ext/src/MutatingScope.cpp index 1138b46ed9a..749ac1410d8 100644 --- a/turbo-ext/src/MutatingScope.cpp +++ b/turbo-ext/src/MutatingScope.cpp @@ -389,6 +389,10 @@ static zval *pt_ms_service_of(zval *services, uint32_t which) return slot; } +/* public const UNKNOWN_CLOSURE_BIND_SCOPE_CLASS (support.h) */ +#define PT_MS_UNKNOWN_CLOSURE_BIND_SCOPE_CLASS PT_UNKNOWN_CLOSURE_BIND_SCOPE_CLASS +zend_string *pt_unknown_closure_bind_scope_class = nullptr; + /* private const COMPLEX_UNION_TYPE_MEMBER_LIMIT */ #define PT_MS_COMPLEX_UNION_TYPE_MEMBER_LIMIT 8 @@ -434,6 +438,7 @@ static void ZEND_FASTCALL msWithoutMemoizedTypes(INTERNAL_FUNCTION_PARAMETERS); static void ZEND_FASTCALL msGetNativeType(INTERNAL_FUNCTION_PARAMETERS); static void ZEND_FASTCALL msDoNotTreatPhpDocTypesAsCertain(INTERNAL_FUNCTION_PARAMETERS); static void ZEND_FASTCALL msResolveName(INTERNAL_FUNCTION_PARAMETERS); +static void ZEND_FASTCALL msGetClosureBindScopeClassReflection(INTERNAL_FUNCTION_PARAMETERS); static void ZEND_FASTCALL msResolveTypeByName(INTERNAL_FUNCTION_PARAMETERS); static void ZEND_FASTCALL msGetParentScope(INTERNAL_FUNCTION_PARAMETERS); static void ZEND_FASTCALL msPushInFunctionCall(INTERNAL_FUNCTION_PARAMETERS); @@ -741,6 +746,7 @@ class MutatingScope return thisCall(PT_LC("getparentscope"), msGetParentScope, 0, NULL, [&]() { return getParentScope(); }); } zv::Val thisDoNotTreatPhpDocTypesAsCertain() { return thisCall(PT_LC("donottreatphpdoctypesascertain"), msDoNotTreatPhpDocTypesAsCertain, 0, NULL, [&]() { return doNotTreatPhpDocTypesAsCertain(); }); } + zv::Val thisGetClosureBindScopeClassReflection() { return thisCall(PT_LC("getclosurebindscopeclassreflection"), msGetClosureBindScopeClassReflection, 0, NULL, [&]() { return getClosureBindScopeClassReflection(); }); } zv::Val thisResolveName(zval *name) { return thisCall(PT_LC("resolvename"), msResolveName, 1, name, [&]() { return resolveName(Z_OBJ_P(name)); }); } zv::Val thisResolveTypeByName(zval *name) { return thisCall(PT_LC("resolvetypebyname"), msResolveTypeByName, 1, name, [&]() { return resolveTypeByName(Z_OBJ_P(name)); }); } zv::Val thisGetPhpVersion() { return thisCall(PT_LC("getphpversion"), msGetPhpVersion, 0, NULL, [&]() { return getPhpVersion(); }); } @@ -3211,27 +3217,42 @@ class MutatingScope ZVAL_OBJ(&nameZv, name); zv::Str originalClass = zv::Str::adopt(pt_name_node_cast_string(&nameZv)); if (UNEXPECTED(originalClass.isNull())) return zv::Val(); - bool inClass; - if (UNEXPECTED(!thisIsInClass(inClass))) return zv::Val(); - if (inClass) { - zv::Str lowerClass = zv::Str::adopt(zend_string_tolower(originalClass.get())); - if (zend_string_equals_literal(lowerClass.get(), "self") || zend_string_equals_literal(lowerClass.get(), "static")) { - if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES, "inClosureBindScopeClasses"))) return zv::Val(); - HashTable *bindScopeClasses = Z_ARRVAL_P(slot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES).raw()); - if (zend_hash_num_elements(bindScopeClasses) != 0 && !isSingleStringList(bindScopeClasses, PT_LC("static"))) { - zend_string *first = firstBindScopeClass(bindScopeClasses, "resolveName"); - if (UNEXPECTED(first == NULL)) return zv::Val(); - return zv::Val::string(first); - } + zv::Str lowerClass = zv::Str::adopt(zend_string_tolower(originalClass.get())); + + if (isSpecialClassName(lowerClass.get())) { + // bound to a class that is not exactly one known class: neither the enclosing + // class nor any one of the candidates + bool ambiguous; + if (UNEXPECTED(!isClosureBindScopeClassAmbiguous(ambiguous))) return zv::Val(); + if (ambiguous) return zv::Val::string(originalClass.get()); + } + + zv::Val closureBindScopeClassName = getClosureBindScopeClassName(); + if (UNEXPECTED(closureBindScopeClassName.isUndef())) return zv::Val(); + if (zend_string_equals_literal(lowerClass.get(), "self") || zend_string_equals_literal(lowerClass.get(), "static")) { + if (!closureBindScopeClassName.isNull()) return closureBindScopeClassName; + bool inClass; + if (UNEXPECTED(!thisIsInClass(inClass))) return zv::Val(); + if (inClass) { zv::Val classReflection = thisGetClassReflection(); if (UNEXPECTED(classReflection.isUndef())) return zv::Val(); zend_object *reflection = requireObject(classReflection, "getName"); if (UNEXPECTED(reflection == NULL)) return zv::Val(); return pt_class_reflection_get_name(reflection); - } else if (zend_string_equals_literal(lowerClass.get(), "parent")) { - zv::Val classReflection = thisGetClassReflection(); - if (UNEXPECTED(classReflection.isUndef())) return zv::Val(); - zend_object *reflection = requireObject(classReflection, "getParentClass"); + } + } else if (zend_string_equals_literal(lowerClass.get(), "parent")) { + zv::Val currentClassReflection = thisGetClosureBindScopeClassReflection(); + if (UNEXPECTED(currentClassReflection.isUndef())) return zv::Val(); + if (currentClassReflection.isNull() && closureBindScopeClassName.isNull()) { + bool inClass; + if (UNEXPECTED(!thisIsInClass(inClass))) return zv::Val(); + if (inClass) { + currentClassReflection = thisGetClassReflection(); + if (UNEXPECTED(currentClassReflection.isUndef())) return zv::Val(); + } + } + if (!currentClassReflection.isNull()) { + zend_object *reflection = requireObject(currentClassReflection, "getParentClass"); if (UNEXPECTED(reflection == NULL)) return zv::Val(); zv::Val parentClass = pt_type_call(reflection, PT_LC("getparentclass"), 0, NULL); if (UNEXPECTED(parentClass.isUndef())) return zv::Val(); @@ -3257,37 +3278,46 @@ class MutatingScope zv::Val lower = pt_name_node_to_lower_string(&nameNodeZv); if (UNEXPECTED(lower.isUndef())) return zv::Val(); if (Z_TYPE_P(lower.raw()) == IS_STRING && zend_string_equals_literal(Z_STR_P(lower.raw()), "static")) { + zv::Val closureBindScopeClassReflection = thisGetClosureBindScopeClassReflection(); + if (UNEXPECTED(closureBindScopeClassReflection.isUndef())) return zv::Val(); + if (!closureBindScopeClassReflection.isNull()) { + return newStaticType(closureBindScopeClassReflection, "StaticType"); + } bool inClass; if (UNEXPECTED(!thisIsInClass(inClass))) return zv::Val(); if (inClass) { - if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES, "inClosureBindScopeClasses"))) return zv::Val(); - HashTable *bindScopeClasses = Z_ARRVAL_P(slot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES).raw()); - if (zend_hash_num_elements(bindScopeClasses) != 0 && !isSingleStringList(bindScopeClasses, PT_LC("static"))) { - zval *first = zend_hash_index_find(bindScopeClasses, 0); - zval nullZv; - ZVAL_NULL(&nullZv); - if (first == NULL) { - zend_error(E_WARNING, "Undefined array key 0"); - if (UNEXPECTED(EG(exception))) return zv::Val(); - first = &nullZv; - } - zv::Ref provider = service(PT_MSS_REFLECTION_PROVIDER); - if (UNEXPECTED(!provider.isObject())) return uninitializedProperty("reflectionProvider"); - bool hasClass; - if (UNEXPECTED(!pt_reflection_provider_has_class(provider.asObject(), first, hasClass))) return zv::Val(); - if (hasClass) { - zv::Val classReflection = pt_reflection_provider_get_class(provider.asObject(), first); - if (UNEXPECTED(classReflection.isUndef())) return zv::Val(); - return newStaticType(classReflection, "StaticType"); - } - } - zv::Val classReflection = thisGetClassReflection(); if (UNEXPECTED(classReflection.isUndef())) return zv::Val(); return newStaticType(classReflection, "StaticType"); } } + if (Z_TYPE_P(lower.raw()) == IS_STRING && isSpecialClassName(Z_STR_P(lower.raw()))) { + bool ambiguous; + if (UNEXPECTED(!isClosureBindScopeClassAmbiguous(ambiguous))) return zv::Val(); + if (ambiguous) { + // bound to a class that is not exactly one known class: the closest class all + // candidates are or extend, if any + zv::Val commonAncestor = getClosureBindScopeCommonAncestor(name); + if (UNEXPECTED(commonAncestor.isUndef())) return zv::Val(); + if (!commonAncestor.isNull()) { + if (zend_string_equals_literal(Z_STR_P(lower.raw()), "static")) return newStaticType(commonAncestor, "StaticType"); + zv::Val ancestorName = pt_class_reflection_get_name(Z_OBJ_P(commonAncestor.raw())); + if (UNEXPECTED(ancestorName.isUndef())) return zv::Val(); + return pt_type_new_object_type(ancestorName.raw()); + } + + /* new ObjectType((string) $name) */ + zval nameStringZv; + ZVAL_OBJ(&nameStringZv, name); + zv::Str nameString = zv::Str::adopt(pt_name_node_cast_string(&nameStringZv)); + if (UNEXPECTED(nameString.isNull())) return zv::Val(); + zval nameStringValue; + ZVAL_STR(&nameStringValue, nameString.get()); + return pt_type_new_object_type(&nameStringValue); + } + } + zval nameZv; ZVAL_OBJ(&nameZv, name); zv::Val originalClass = thisResolveName(&nameZv); @@ -3296,12 +3326,24 @@ class MutatingScope zend_type_error("PHPStan\\Analyser\\MutatingScope::resolveName(): Return value must be of type string, %s returned", zend_zval_value_name(originalClass.raw())); return zv::Val(); } + // outside a class only self/parent/static follow the Closure::bind() scope; + // inside one any name of the bound class does, as before + if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES, "inClosureBindScopeClasses"))) return zv::Val(); + HashTable *bindScopeClasses = Z_ARRVAL_P(slot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES).raw()); bool inClass; - if (UNEXPECTED(!thisIsInClass(inClass))) return zv::Val(); - if (inClass) { - if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES, "inClosureBindScopeClasses"))) return zv::Val(); - HashTable *bindScopeClasses = Z_ARRVAL_P(slot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES).raw()); - if (isSingleStringList(bindScopeClasses, ZSTR_VAL(Z_STR_P(originalClass.raw())), ZSTR_LEN(Z_STR_P(originalClass.raw())))) { + if (isSingleStringList(bindScopeClasses, ZSTR_VAL(Z_STR_P(originalClass.raw())), ZSTR_LEN(Z_STR_P(originalClass.raw())))) { + if (UNEXPECTED(!thisIsInClass(inClass))) return zv::Val(); + bool followsBindScope = inClass; + if (!followsBindScope) { + /* $name->isSpecialClassName() */ + zv::Val lowerName = pt_name_node_to_lower_string(&nameZv); + if (UNEXPECTED(lowerName.isUndef())) return zv::Val(); + followsBindScope = Z_TYPE_P(lowerName.raw()) == IS_STRING + && (zend_string_equals_literal(Z_STR_P(lowerName.raw()), "self") + || zend_string_equals_literal(Z_STR_P(lowerName.raw()), "parent") + || zend_string_equals_literal(Z_STR_P(lowerName.raw()), "static")); + } + if (followsBindScope) { zv::Ref provider = service(PT_MSS_REFLECTION_PROVIDER); if (UNEXPECTED(!provider.isObject())) return uninitializedProperty("reflectionProvider"); bool hasClass; @@ -3313,7 +3355,9 @@ class MutatingScope } return pt_type_new_object_type(originalClass.raw()); } - + } + if (UNEXPECTED(!thisIsInClass(inClass))) return zv::Val(); + if (inClass) { zv::Val classReflection = thisGetClassReflection(); if (UNEXPECTED(classReflection.isUndef())) return zv::Val(); zv::Val thisType = newThisType(classReflection); @@ -3326,6 +3370,130 @@ class MutatingScope return pt_type_new_object_type(originalClass.raw()); } + /* Mirrors getClosureBindScopeClassName(): $this->inClosureBindScopeClasses[0] + * unless the list is empty or ['static']; IS_NULL for null, UNDEF = pending + * exception */ + zv::Val getClosureBindScopeClassName() + { + if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES, "inClosureBindScopeClasses"))) return zv::Val(); + HashTable *bindScopeClasses = Z_ARRVAL_P(slot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES).raw()); + if (zend_hash_num_elements(bindScopeClasses) == 0 || isSingleStringList(bindScopeClasses, PT_LC("static"))) return zv::Val::null(); + bool ambiguous; + if (UNEXPECTED(!isClosureBindScopeClassAmbiguous(ambiguous))) return zv::Val(); + if (ambiguous) return zv::Val::null(); + + zend_string *first = firstBindScopeClass(bindScopeClasses, "getClosureBindScopeClassName"); + if (UNEXPECTED(first == NULL)) return zv::Val(); + return zv::Val::string(first); + } + + /* whether $this->inClosureBindScopeClasses holds UNKNOWN_CLOSURE_BIND_SCOPE_CLASS + * (in_array(..., true)) */ + bool bindScopeClassesHoldUnknown() + { + zv::Ref bindScopeClasses = slot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES); + if (!bindScopeClasses.isArray()) return false; + for (auto entry : zv::TableRef(bindScopeClasses.asArrayTable())) { + zval *className = entry.value().deref().raw(); + if (Z_TYPE_P(className) == IS_STRING && zend_string_equals_literal(Z_STR_P(className), PT_MS_UNKNOWN_CLOSURE_BIND_SCOPE_CLASS)) return true; + } + return false; + } + + /* Name::isSpecialClassName() of a lowercased name */ + static bool isSpecialClassName(zend_string *lower) + { + return zend_string_equals_literal(lower, "self") || zend_string_equals_literal(lower, "static") || zend_string_equals_literal(lower, "parent"); + } + + /* Mirrors isClosureBindScopeClassAmbiguous(); false = pending exception */ + [[nodiscard]] bool isClosureBindScopeClassAmbiguous(bool &out) + { + if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES, "inClosureBindScopeClasses"))) return false; + HashTable *bindScopeClasses = Z_ARRVAL_P(slot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES).raw()); + out = zend_hash_num_elements(bindScopeClasses) > 1 || bindScopeClassesHoldUnknown(); + return true; + } + + /* Mirrors getClosureBindScopeCommonAncestor(): in an ambiguous scope, the + * closest class all candidates for self/static (or, for parent, their + * parent classes) are or extend; IS_NULL for null, UNDEF = pending + * exception */ + zv::Val getClosureBindScopeCommonAncestor(zend_object *name) + { + zval nameZv; + ZVAL_OBJ(&nameZv, name); + zv::Val lower = pt_name_node_to_lower_string(&nameZv); + if (UNEXPECTED(lower.isUndef())) return zv::Val(); + if (Z_TYPE_P(lower.raw()) != IS_STRING || !isSpecialClassName(Z_STR_P(lower.raw()))) return zv::Val::null(); + bool ambiguous; + if (UNEXPECTED(!isClosureBindScopeClassAmbiguous(ambiguous))) return zv::Val(); + if (!ambiguous) return zv::Val::null(); + + bool isParent = zend_string_equals_literal(Z_STR_P(lower.raw()), "parent"); + zval *bindScopeClassesZv = slot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES).raw(); + zv::Ref provider = service(PT_MSS_REFLECTION_PROVIDER); + if (UNEXPECTED(!provider.isObject())) return uninitializedProperty("reflectionProvider"); + zv::Arr classReflections = zv::Arr::create(zend_hash_num_elements(Z_ARRVAL_P(bindScopeClassesZv))); + for (auto entry : zv::ArrRef(bindScopeClassesZv)) { + zval *className = entry.value().deref().raw(); + if (Z_TYPE_P(className) == IS_STRING && zend_string_equals_literal(Z_STR_P(className), PT_MS_UNKNOWN_CLOSURE_BIND_SCOPE_CLASS)) return zv::Val::null(); + bool hasClass; + if (UNEXPECTED(!pt_reflection_provider_has_class(provider.asObject(), className, hasClass))) return zv::Val(); + if (!hasClass) return zv::Val::null(); + zv::Val classReflection = pt_reflection_provider_get_class(provider.asObject(), className); + if (UNEXPECTED(classReflection.isUndef())) return zv::Val(); + if (isParent) { + zend_object *classReflectionObject = requireObject(classReflection, "getParentClass"); + if (UNEXPECTED(classReflectionObject == NULL)) return zv::Val(); + classReflection = pt_type_call(classReflectionObject, PT_LC("getparentclass"), 0, NULL); + if (UNEXPECTED(classReflection.isUndef())) return zv::Val(); + if (classReflection.isNull()) return zv::Val::null(); + } + classReflections.push(std::move(classReflection)); + } + + zv::Val candidate = zv::Val::copyOf(zv::Ref(zend_hash_index_find(classReflections.table(), 0))); + while (!candidate.isNull()) { + zend_object *candidateObject = requireObject(candidate, "getName"); + if (UNEXPECTED(candidateObject == NULL)) return zv::Val(); + zv::Val candidateName = pt_class_reflection_get_name(candidateObject); + if (UNEXPECTED(candidateName.isUndef())) return zv::Val(); + bool sharedByAll = true; + for (auto entry : zv::ArrRef(classReflections.raw())) { + zval *classReflection = entry.value().deref().raw(); + zv::Val name_ = pt_class_reflection_get_name(Z_OBJ_P(classReflection)); + if (UNEXPECTED(name_.isUndef())) return zv::Val(); + if (Z_TYPE_P(name_.raw()) == IS_STRING && Z_TYPE_P(candidateName.raw()) == IS_STRING && zend_string_equals(Z_STR_P(name_.raw()), Z_STR_P(candidateName.raw()))) continue; + bool isSubclass; + if (UNEXPECTED(!pt_class_reflection_is_subclass_of_class(Z_OBJ_P(classReflection), candidate.raw(), isSubclass))) return zv::Val(); + if (!isSubclass) { + sharedByAll = false; + break; + } + } + if (sharedByAll) return candidate; + candidate = pt_type_call(candidateObject, PT_LC("getparentclass"), 0, NULL); + if (UNEXPECTED(candidate.isUndef())) return zv::Val(); + } + + return zv::Val::null(); + } + + /* Mirrors getClosureBindScopeClassReflection(); IS_NULL for null, UNDEF = + * pending exception */ + zv::Val getClosureBindScopeClassReflection() + { + zv::Val className = getClosureBindScopeClassName(); + if (className.isUndef() || className.isNull()) return className; + zv::Ref provider = service(PT_MSS_REFLECTION_PROVIDER); + if (UNEXPECTED(!provider.isObject())) return uninitializedProperty("reflectionProvider"); + bool hasClass; + if (UNEXPECTED(!pt_reflection_provider_has_class(provider.asObject(), className.raw(), hasClass))) return zv::Val(); + if (!hasClass) return zv::Val::null(); + return pt_reflection_provider_get_class(provider.asObject(), className.raw()); + } + /* new StaticType($classReflection) / new ThisType($classReflection): * the constructors' TypeError on anything but a ClassReflection */ static zv::Val newStaticType(zv::Val &classReflection, const char *className) @@ -3886,35 +4054,37 @@ class MutatingScope bool isName; if (UNEXPECTED(!isInstance(zv::Ref(type), PT_CLASS_NAME, isName))) return zv::Val(); if (isName) { - if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES, "inClosureBindScopeClasses"))) return zv::Val(); - HashTable *bindScopeClasses = Z_ARRVAL_P(slot(PT_MS_PROP_IN_CLOSURE_BIND_SCOPE_CLASSES).raw()); - if (zend_hash_num_elements(bindScopeClasses) != 0 && !isSingleStringList(bindScopeClasses, PT_LC("static"))) { - zv::Val lower = pt_name_node_to_lower_string(type); - if (UNEXPECTED(lower.isUndef())) return zv::Val(); - bool isRelativeName = lower.ref().isString() - && (zend_string_equals_literal(lower.ref().asString(), "static") - || zend_string_equals_literal(lower.ref().asString(), "self") - || zend_string_equals_literal(lower.ref().asString(), "parent")); - if (isRelativeName) { - zval nullZv; - ZVAL_NULL(&nullZv); - zval *first = zend_hash_index_find(bindScopeClasses, 0); - if (UNEXPECTED(first == NULL)) { - zend_error(E_WARNING, "Undefined array key 0"); - if (UNEXPECTED(EG(exception))) return zv::Val(); - first = &nullZv; - } - zv::Ref provider = service(PT_MSS_REFLECTION_PROVIDER); - if (UNEXPECTED(!provider.isObject())) return uninitializedProperty("reflectionProvider"); - bool hasClass; - if (UNEXPECTED(!pt_reflection_provider_has_class(provider.asObject(), first, hasClass))) return zv::Val(); - if (hasClass) { - zv::Val classReflection = pt_reflection_provider_get_class(provider.asObject(), first); - if (UNEXPECTED(classReflection.isUndef())) return zv::Val(); - zv::Val context = pt_initializer_expr_context_from_class_reflection(classReflection.raw()); + zv::Val lower = pt_name_node_to_lower_string(type); + if (UNEXPECTED(lower.isUndef())) return zv::Val(); + if (lower.ref().isString() && isSpecialClassName(lower.ref().asString())) { + bool ambiguous; + if (UNEXPECTED(!isClosureBindScopeClassAmbiguous(ambiguous))) return zv::Val(); + if (ambiguous) { + // bound to a class that is not exactly one known class: the closest class all + // candidates are or extend, or nothing known + zv::Val commonAncestor = getClosureBindScopeCommonAncestor(Z_OBJ_P(type)); + if (UNEXPECTED(commonAncestor.isUndef())) return zv::Val(); + if (commonAncestor.isNull()) return pt_type_new_mixed_type(); + if (zend_string_equals_literal(lower.ref().asString(), "static")) { + zv::Val context = pt_initializer_expr_context_from_class_reflection(commonAncestor.raw()); if (UNEXPECTED(context.isUndef())) return zv::Val(); return initializerExprTypeResolverFunctionType(type, isNullable, context.raw()); } + zv::Val ancestorName = pt_class_reflection_get_name(Z_OBJ_P(commonAncestor.raw())); + if (UNEXPECTED(ancestorName.isUndef())) return zv::Val(); + zv::Val fullyQualified = pt_name_node_new(PT_CLASS_FULLY_QUALIFIED, ancestorName.raw()); + if (UNEXPECTED(fullyQualified.isUndef())) return zv::Val(); + zv::Val context = pt_initializer_expr_context_from_scope(thisZval()); + if (UNEXPECTED(context.isUndef())) return zv::Val(); + return initializerExprTypeResolverFunctionType(fullyQualified.raw(), isNullable, context.raw()); + } + + zv::Val closureBindScopeClassReflection = thisGetClosureBindScopeClassReflection(); + if (UNEXPECTED(closureBindScopeClassReflection.isUndef())) return zv::Val(); + if (!closureBindScopeClassReflection.isNull()) { + zv::Val context = pt_initializer_expr_context_from_class_reflection(closureBindScopeClassReflection.raw()); + if (UNEXPECTED(context.isUndef())) return zv::Val(); + return initializerExprTypeResolverFunctionType(type, isNullable, context.raw()); } } } @@ -4862,7 +5032,7 @@ class MutatingScope } zv::Val classNames = pt_type_op(Z_OBJ_P(thisType), PT_OP_GET_OBJECT_CLASS_NAMES, 0, NULL); if (UNEXPECTED(classNames.isUndef())) return zv::Val(); - return createWithTablesAndBindScopeClasses(tables, std::move(classNames)); + return createWithTablesAndBindScopeClasses(tables, closureBindScopeClassesOf(std::move(classNames))); } /** @api */ @@ -4878,10 +5048,21 @@ class MutatingScope CreateArgs a; if (UNEXPECTED(!fillFromSlots(a))) return zv::Val(); if (UNEXPECTED(!fillDispatched(a, true, true))) return zv::Val(); - a.set(CreateArgs::IN_CLOSURE_BIND_SCOPE_CLASSES, zv::Ref(scopeClasses)); + zv::Val scopeClassesOf = closureBindScopeClassesOf(zv::Val::copyOf(zv::Ref(scopeClasses))); + a.set(CreateArgs::IN_CLOSURE_BIND_SCOPE_CLASSES, scopeClassesOf.ref()); return scopeFactoryCreate(a); } + /* Mirrors closureBindScopeClassesOf(): [] becomes + * [UNKNOWN_CLOSURE_BIND_SCOPE_CLASS] */ + static zv::Val closureBindScopeClassesOf(zv::Val classNames) + { + if (Z_TYPE_P(classNames.raw()) != IS_ARRAY || zend_hash_num_elements(Z_ARRVAL_P(classNames.raw())) != 0) return classNames; + zv::Arr unknown = zv::Arr::create(1); + unknown.push(zv::Val::string(pt_unknown_closure_bind_scope_class)); + return zv::Val(std::move(unknown)); + } + /* }}} */ /* }}} */ @@ -10004,6 +10185,11 @@ class MutatingScope (void) uninitializedProperty("reflectionProvider"); return false; } + if (bindScopeClassesHoldUnknown()) { + // bound to an unknown class, which may be the one declaring the member + out = true; + return true; + } for (auto entry : zv::TableRef(inClosureBindScopeClasses.asArrayTable())) { zval *className = entry.value().deref().raw(); bool hasClass; @@ -11358,6 +11544,12 @@ static void ZEND_FASTCALL msResolveName(INTERNAL_FUNCTION_PARAMETERS) PT_RETURN_VAL(PT_THIS.resolveName(Z_OBJ_P(name))); } +static void ZEND_FASTCALL msGetClosureBindScopeClassReflection(INTERNAL_FUNCTION_PARAMETERS) +{ + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(PT_THIS.getClosureBindScopeClassReflection()); +} + static void ZEND_FASTCALL msResolveTypeByName(INTERNAL_FUNCTION_PARAMETERS) { PT_MS_PARSE_NAME(name); @@ -12219,6 +12411,26 @@ zv::Val pt_mutating_scope_resolve_name(zend_object *scope, zend_object *name) return pt_type_call(scope, PT_LC("resolvename"), 1, &nameZv); } +bool pt_mutating_scope_is_closure_bind_scope_class_ambiguous(zend_object *scope, bool &out) +{ + if (PT_MS_INHERITS(scope, "isclosurebindscopeclassambiguous")) return MutatingScope(scope).isClosureBindScopeClassAmbiguous(out); + return msCallBoolResult(scope, PT_LC("isclosurebindscopeclassambiguous"), 0, NULL, out); +} + +zv::Val pt_mutating_scope_get_closure_bind_scope_common_ancestor(zend_object *scope, zend_object *name) +{ + if (PT_MS_INHERITS(scope, "getclosurebindscopecommonancestor")) return MutatingScope(scope).getClosureBindScopeCommonAncestor(name); + zval nameZv; + ZVAL_OBJ(&nameZv, name); + return pt_type_call(scope, PT_LC("getclosurebindscopecommonancestor"), 1, &nameZv); +} + +zv::Val pt_mutating_scope_get_closure_bind_scope_class_reflection(zend_object *scope) +{ + if (PT_MS_NATIVE(scope, PT_LC("getclosurebindscopeclassreflection"), msGetClosureBindScopeClassReflection)) return MutatingScope(scope).getClosureBindScopeClassReflection(); + return pt_type_call(scope, PT_LC("getclosurebindscopeclassreflection"), 0, NULL); +} + zv::Val pt_mutating_scope_enter_closure_bind(zend_object *scope, zval *thisType, zval *nativeThisType, zval *scopeClasses) { zval nullZv; @@ -12277,6 +12489,8 @@ PT_MINIT_REGISTRATION(pt_register_mutating_scope) * may too), so every method stays dispatched through the object's * class entry */ ptdecl::MutatingScope::declareClass(cls); + cls.publicClassConstantString("UNKNOWN_CLOSURE_BIND_SCOPE_CLASS", PT_UNKNOWN_CLOSURE_BIND_SCOPE_CLASS); + pt_unknown_closure_bind_scope_class = zend_string_init_interned(PT_LC(PT_UNKNOWN_CLOSURE_BIND_SCOPE_CLASS), 1); /* {{{ the slots, in the twin's declaration order (the PT_MS_PROP_* * enum): the class-body properties with their defaults, then the @@ -12571,6 +12785,7 @@ PT_MINIT_REGISTRATION(pt_register_mutating_scope) cls.method(sigs::doNotTreatPhpDocTypesAsCertain, msDoNotTreatPhpDocTypesAsCertain); cls.method(sigs::resolveName, msResolveName); + cls.method(sigs::getClosureBindScopeClassReflection, msGetClosureBindScopeClassReflection); cls.method(sigs::resolveTypeByName, msResolveTypeByName); cls.method<&MutatingScope::getTypeFromValue, zp::Zval>(sigs::getTypeFromValue); @@ -12833,6 +13048,16 @@ PT_MINIT_REGISTRATION(pt_register_mutating_scope) cls.method<&MutatingScope::enterClosureCall, zp::Obj, zp::Obj>(sigs::enterClosureCall); + cls.method(sigs::getClosureBindScopeCommonAncestor, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_MS_PARSE_NAME(name); + PT_RETURN_VAL(PT_THIS.getClosureBindScopeCommonAncestor(Z_OBJ_P(name))); + }); + + cls.method(sigs::isClosureBindScopeClassAmbiguous, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + PT_MS_RETURN_BOOL(PT_THIS.isClosureBindScopeClassAmbiguous(out_)); + }); + cls.method(sigs::isInClosureBind, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); PT_MS_RETURN_BOOL(PT_THIS.isInClosureBind(out_)); diff --git a/turbo-ext/src/NewHandler.cpp b/turbo-ext/src/NewHandler.cpp index 42e18fc70a4..5f8e5515d87 100644 --- a/turbo-ext/src/NewHandler.cpp +++ b/turbo-ext/src/NewHandler.cpp @@ -977,6 +977,36 @@ class NewHandler if (preResolvedAcceptor != NULL && Z_TYPE_P(preResolvedAcceptor) == IS_NULL) preResolvedAcceptor = NULL; if (argsResult != NULL && Z_TYPE_P(argsResult) == IS_NULL) argsResult = NULL; + { + int isSpecial = nameLowerEquals(className, PT_LC("self")); + if (isSpecial == 0) isSpecial = nameLowerEquals(className, PT_LC("static")); + if (isSpecial == 0) isSpecial = nameLowerEquals(className, PT_LC("parent")); + if (UNEXPECTED(isSpecial < 0)) return zv::Val(); + bool ambiguous = false; + if (isSpecial == 1 && UNEXPECTED(!pt_mutating_scope_is_closure_bind_scope_class_ambiguous(Z_OBJ_P(scope), ambiguous))) return zv::Val(); + if (ambiguous) { + // bound to a class that is not exactly one known class: an instance of the + // closest class all candidates extend, or of some class + zv::Val commonAncestor = pt_mutating_scope_get_closure_bind_scope_common_ancestor(Z_OBJ_P(scope), Z_OBJ_P(className)); + if (UNEXPECTED(commonAncestor.isUndef())) return zv::Val(); + if (commonAncestor.isNull()) { + zval objectType; + if (UNEXPECTED(!pt_object_without_class_type_new(&objectType))) return zv::Val(); + return zv::Val::adopt(objectType); + } + int isStaticAncestor = nameLowerEquals(className, PT_LC("static")); + if (UNEXPECTED(isStaticAncestor < 0)) return zv::Val(); + if (isStaticAncestor == 1) { + zval staticType; + if (UNEXPECTED(!pt_static_type_new(&staticType, commonAncestor.raw()))) return zv::Val(); + return zv::Val::adopt(staticType); + } + zv::Val ancestorName = pt_class_reflection_get_name(Z_OBJ_P(commonAncestor.raw())); + if (UNEXPECTED(ancestorName.isUndef())) return zv::Val(); + return pt_type_new_object_type(ancestorName.raw()); + } + } + zv::Val resolvedClassName = pt_mutating_scope_resolve_name(Z_OBJ_P(scope), Z_OBJ_P(className)); if (UNEXPECTED(resolvedClassName.isUndef())) return zv::Val(); if (UNEXPECTED(!requireString(resolvedClassName.raw(), "PHPStan\\Analyser\\MutatingScope::resolveName", "name"))) return zv::Val(); diff --git a/turbo-ext/src/ParametersAcceptorSelector.cpp b/turbo-ext/src/ParametersAcceptorSelector.cpp index c1869f628bd..751d529d707 100644 --- a/turbo-ext/src/ParametersAcceptorSelector.cpp +++ b/turbo-ext/src/ParametersAcceptorSelector.cpp @@ -1959,9 +1959,25 @@ class ParametersAcceptorSelector } { + // the closure is the 1st argument unless the call names its arguments + zval *closureBindArgNode = firstArg; + zv::Val nameHold; + zval *name = readProperty(pt_pas_arg_name_site, firstArg, PT_LC("name"), nameHold); + if (UNEXPECTED(name == NULL)) return zv::Val(); + if (Z_TYPE_P(name) != IS_NULL) { + for (auto entry : zv::ArrRef(argsZv)) { + zval *arg = entry.value().deref().raw(); + zval *attribute = NULL; + if (UNEXPECTED(!getAttribute(arg, pt_pas_closure_bind_arg, attribute))) return zv::Val(); + if (attribute != NULL) { + closureBindArgNode = arg; + break; + } + } + } zval *closureBindArg = NULL; - if (UNEXPECTED(!getAttribute(firstArg, pt_pas_closure_bind_arg, closureBindArg))) return zv::Val(); - if (closureBindArg != NULL && UNEXPECTED(!applyClosureBindArg(parametersAcceptors, firstArg, scope))) return zv::Val(); + if (UNEXPECTED(!getAttribute(closureBindArgNode, pt_pas_closure_bind_arg, closureBindArg))) return zv::Val(); + if (closureBindArg != NULL && UNEXPECTED(!applyClosureBindArg(parametersAcceptors, closureBindArgNode, scope))) return zv::Val(); } return parametersAcceptors; @@ -2417,10 +2433,10 @@ class ParametersAcceptorSelector } /* the ClosureBindArgVisitor override; false = pending exception */ - static bool applyClosureBindArg(zv::Val ¶metersAcceptors, zval *firstArg, zval *scope) + static bool applyClosureBindArg(zv::Val ¶metersAcceptors, zval *closureBindArg, zval *scope) { zv::Val valueHold; - zval *value = argValueOf(firstArg, valueHold); + zval *value = argValueOf(closureBindArg, valueHold); if (UNEXPECTED(value == NULL)) return false; bool isVariable = false; if (UNEXPECTED(!isA(value, PT_CLASS_VARIABLE, isVariable))) return false; diff --git a/turbo-ext/src/StaticCallHandler.cpp b/turbo-ext/src/StaticCallHandler.cpp index b951a7df69d..57f58046a64 100644 --- a/turbo-ext/src/StaticCallHandler.cpp +++ b/turbo-ext/src/StaticCallHandler.cpp @@ -6,9 +6,10 @@ * arginfo so Nette autowires it. processExpr() is registered as the class's * handler entry (Engine.h). The twin's closures are native closures capturing * what the PHP closures capture: the Closure::bind() scope factory handed to - * processArgs() ($expr, $storage; its inner $readArgType closure, created and - * called only inside it, is inlined), the typeCallback ($this, $beforeScope, - * $expr, $classResult, $nameResult, $resolvedParametersAcceptor, $argsResult; + * processArgs() ($expr, $storage, $parametersAcceptor; its inner $readArgType + * closure, created and called only inside it, is inlined), the typeCallback + * ($this, $beforeScope, $expr, $classResult, $nameResult, + * $resolvedParametersAcceptor, $argsResult; * none for the early-terminating `new NeverType(true)` one), the * specifyTypesCallback ($this, $beforeScope, $expr, $normalizedExpr, * $classResult, $resolvedParametersAcceptor, $argsResult), the @@ -438,7 +439,7 @@ class StaticCallHandler return zv::Val(); } if (zend_string_equals_literal_ci(Z_STR_P(methodNameHold.raw()), "bind")) { - closureBindScopeFactory = pt_native_closure(&closureBindScopeFactoryBody, expr, storage); + closureBindScopeFactory = pt_native_closure(&closureBindScopeFactoryBody, expr, storage, parametersAcceptor.raw()); } } } else { @@ -1307,9 +1308,9 @@ class StaticCallHandler return currentScope; } - /* static function (MutatingScope $boundScope) use ($expr, $storage): - * MutatingScope — the Closure::bind() scope factory; captures: $expr, - * $storage */ + /* static function (MutatingScope $boundScope) use ($expr, $storage, + * $parametersAcceptor): MutatingScope — the Closure::bind() scope factory; + * captures: $expr, $storage, $parametersAcceptor */ static void closureBindScopeFactoryBody(zval *captures, uint32_t argc, zval *argv, zval *return_value) { if (UNEXPECTED(!requireArguments(argc, 1, "PHPStan\\Analyser\\ExprHandler\\StaticCallHandler::{closure}"))) return; @@ -1318,8 +1319,12 @@ class StaticCallHandler zend_type_error("PHPStan\\Analyser\\ExprHandler\\StaticCallHandler::{closure}(): Argument #1 ($boundScope) must be of type PHPStan\\Analyser\\MutatingScope, %s given", zend_zval_value_name(boundScope)); return; } - zval *expr = &captures[0]; zval *storage = &captures[1]; + // normalized so that $newThis and $newScope are found at their + // parameter positions even when the call names its arguments + zv::Val normalizedExpr = reorderStaticCallArguments(&captures[2], &captures[0]); + if (UNEXPECTED(normalizedExpr.isUndef())) return; + zval *expr = normalizedExpr.isNull() ? &captures[0] : normalizedExpr.raw(); zv::Val thisType = zv::Val::null(); zv::Val nativeThisType = zv::Val::null(); @@ -1353,12 +1358,15 @@ class StaticCallHandler list.push(zv::Ref(&staticName)); scopeClasses = zv::Val(std::move(list)); } + bool bindsUnknownScopeClass = false; zval *thirdArg = argAt(args, 2); if (thirdArg != NULL) { zval *value = argValue(thirdArg); if (UNEXPECTED(value == NULL)) return; zv::Val argValueType = readArgType(boundScope, storage, value, false); if (UNEXPECTED(argValueType.isUndef())) return; + // a newScope that may name a class binds to one even when it is unknown + if (UNEXPECTED(!bindsUnknownScopeClassOf(argValueType.raw(), bindsUnknownScopeClass))) return; zv::Val directClassNames = pt_type_op(Z_OBJ_P(argValueType.raw()), PT_OP_GET_OBJECT_CLASS_NAMES, 0, NULL); if (UNEXPECTED(directClassNames.isUndef())) return; @@ -1388,11 +1396,44 @@ class StaticCallHandler scopeClasses = std::move(classNames); } } + if (bindsUnknownScopeClass && zend_hash_num_elements(Z_ARRVAL_P(scopeClasses.raw())) == 0) { + zv::Arr unknown = zv::Arr::create(1); + unknown.push(zv::Val::string(pt_unknown_closure_bind_scope_class)); + scopeClasses = zv::Val(std::move(unknown)); + } zv::Val bound = pt_mutating_scope_enter_closure_bind(Z_OBJ_P(boundScope), thisType.raw(), nativeThisType.raw(), scopeClasses.raw()); if (UNEXPECTED(bound.isUndef())) return; bound.intoReturnValue(return_value); } + /* !TypeCombinator::union(new ConstantStringType('static'), new NullType()) + * ->isSuperTypeOf($argValueType)->yes(): 'static' and null name no class; + * false = pending exception */ + [[nodiscard]] static bool bindsUnknownScopeClassOf(zval *argValueType, bool &out) + { + out = false; + zval staticType; + if (UNEXPECTED(!pt_constant_string_type_new(&staticType, pt_sch_static))) return false; + zval nullType; + if (UNEXPECTED(!pt_null_type_new(&nullType))) { + zval_ptr_dtor(&staticType); + return false; + } + zval noClassMembers[2]; + ZVAL_COPY_VALUE(&noClassMembers[0], &staticType); + ZVAL_COPY_VALUE(&noClassMembers[1], &nullType); + zv::Val noClassType = pt_type_combinator_union(2, noClassMembers); + zval_ptr_dtor(&staticType); + zval_ptr_dtor(&nullType); + if (UNEXPECTED(noClassType.isUndef())) return false; + zv::Val isNoClass = pt_type_op(Z_OBJ_P(noClassType.raw()), PT_OP_IS_SUPER_TYPE_OF, 1, argValueType); + if (UNEXPECTED(isNoClass.isUndef())) return false; + zend_long isNoClassValue = pt_type_result_trinary(isNoClass.raw()); + if (UNEXPECTED(isNoClassValue < 0)) return false; + out = isNoClassValue != PT_TRI_YES; + return true; + } + /* $readArgType($argValue, $useNativeTypes) of the scope factory: the * stored argument result's type on the bound scope, mixed for a missing * result */ diff --git a/turbo-ext/src/generated/MutatingScope.h b/turbo-ext/src/generated/MutatingScope.h index e01ebcdf60c..6618fb319e4 100644 --- a/turbo-ext/src/generated/MutatingScope.h +++ b/turbo-ext/src/generated/MutatingScope.h @@ -208,291 +208,297 @@ inline constexpr char strings[] = "resolveName\0" /* 2739 */ "PHPStan\\Type\\TypeWithClassName\0" /* 2751 */ "resolveTypeByName\0" /* 2782 */ - "value\0" /* 2800 */ - "getTypeFromValue\0" /* 2806 */ - "hasExpressionType\0" /* 2823 */ - "getTrackedExpressionType\0" /* 2841 */ - "reflection\0" /* 2866 */ - "parameter\0" /* 2877 */ - "PHPStan\\Reflection\\ParameterReflection\0" /* 2887 */ - "rememberTypes\0" /* 2926 */ - "pushInFunctionCall\0" /* 2940 */ - "popInFunctionCall\0" /* 2959 */ - "className\0" /* 2977 */ - "isInClassExists\0" /* 2987 */ - "getFunctionCallStack\0" /* 3003 */ - "getFunctionCallStackWithParameters\0" /* 3024 */ - "functionName\0" /* 3059 */ - "isInFunctionExists\0" /* 3072 */ - "classReflection\0" /* 3091 */ - "enterClass\0" /* 3107 */ - "traitReflection\0" /* 3118 */ - "enterTrait\0" /* 3134 */ - "classMethod\0" /* 3145 */ - "PhpParser\\Node\\Stmt\\ClassMethod\0" /* 3157 */ - "templateTypeMap\0" /* 3189 */ - "PHPStan\\Type\\Generic\\TemplateTypeMap\0" /* 3205 */ - "phpDocParameterTypes\0" /* 3242 */ - "phpDocReturnType\0" /* 3263 */ - "throwType\0" /* 3280 */ - "deprecatedDescription\0" /* 3290 */ - "isDeprecated\0" /* 3312 */ - "isInternal\0" /* 3325 */ - "isFinal\0" /* 3336 */ - "isPure\0" /* 3344 */ - "acceptsNamedArguments\0" /* 3351 */ - "asserts\0" /* 3373 */ - "PHPStan\\Reflection\\Assertions\0" /* 3381 */ - "selfOutType\0" /* 3411 */ - "phpDocComment\0" /* 3423 */ - "parameterOutTypes\0" /* 3437 */ - "immediatelyInvokedCallableParameters\0" /* 3455 */ - "phpDocClosureThisTypeParameters\0" /* 3492 */ - "isConstructor\0" /* 3524 */ - "resolvedPhpDocBlock\0" /* 3538 */ - "PHPStan\\PhpDoc\\ResolvedPhpDocBlock\0" /* 3558 */ - "phpDocPureUnlessCallableIsImpureParameters\0" /* 3593 */ - "phpDocPureUnlessParameterPassedParameters\0" /* 3636 */ - "enterClassMethod\0" /* 3678 */ - "hook\0" /* 3695 */ - "PhpParser\\Node\\PropertyHook\0" /* 3700 */ - "propertyName\0" /* 3728 */ - "nativePropertyTypeNode\0" /* 3741 */ - "PhpParser\\Node\\Identifier|PhpParser\\Node\\Name|PhpParser\\Node\\ComplexType\0" /* 3764 */ - "phpDocPropertyType\0" /* 3837 */ - "enterPropertyHook\0" /* 3856 */ - "type\0" /* 3874 */ - "transformStaticType\0" /* 3879 */ - "functionLike\0" /* 3899 */ - "PhpParser\\Node\\FunctionLike\0" /* 3912 */ - "getRealParameterTypes\0" /* 3940 */ - "getRealParameterDefaultValues\0" /* 3962 */ - "PhpParser\\Node\\Stmt\\ClassMethod|PhpParser\\Node\\Stmt\\Function_|PhpParser\\Node\\PropertyHook\0" /* 3992 */ - "getParameterAttributes\0" /* 4082 */ - "PhpParser\\Node\\Stmt\\Function_\0" /* 4105 */ - "pureUnlessCallableIsImpureParameters\0" /* 4135 */ - "pureUnlessParameterPassedParameters\0" /* 4172 */ - "enterFunction\0" /* 4208 */ - "functionReflection\0" /* 4222 */ - "preserveConstructorScope\0" /* 4241 */ - "enterFunctionLike\0" /* 4266 */ - "namespaceName\0" /* 4284 */ - "enterNamespace\0" /* 4298 */ - "thisType\0" /* 4313 */ - "nativeThisType\0" /* 4322 */ - "scopeClasses\0" /* 4337 */ - "enterClosureBind\0" /* 4350 */ - "originalScope\0" /* 4367 */ - "restoreOriginalScopeAfterClosureBind\0" /* 4381 */ - "restoreThisScope\0" /* 4418 */ - "restoreThis\0" /* 4435 */ - "enterClosureCall\0" /* 4447 */ - "isInClosureBind\0" /* 4464 */ - "withClosureBindScopeClasses\0" /* 4480 */ - "closure\0" /* 4508 */ - "PhpParser\\Node\\Expr\\Closure\0" /* 4516 */ - "callableParameters\0" /* 4544 */ - "nativeCallableParameters\0" /* 4563 */ - "enterAnonymousFunction\0" /* 4588 */ - "enterAnonymousFunctionWithoutReflection\0" /* 4611 */ - "typeHolder\0" /* 4651 */ - "PHPStan\\Analyser\\ExpressionTypeHolder\0" /* 4662 */ - "expressionTypeIsUnchangeable\0" /* 4700 */ - "invalidateStaticExpressions\0" /* 4729 */ - "arrowFunction\0" /* 4757 */ - "PhpParser\\Node\\Expr\\ArrowFunction\0" /* 4771 */ - "enterArrowFunction\0" /* 4805 */ - "enterArrowFunctionWithoutReflection\0" /* 4824 */ - "PhpParser\\Node\\Param\0" /* 4860 */ - "isParameterValueNullable\0" /* 4881 */ - "isNullable\0" /* 4906 */ - "isVariadic\0" /* 4917 */ - "getFunctionType\0" /* 4928 */ - "index\0" /* 4944 */ - "getCallableParameterType\0" /* 4950 */ - "startIndex\0" /* 4975 */ - "buildVariadicArrayTypeFromCallableParameters\0" /* 4986 */ - "nativeType\0" /* 5031 */ - "inferredType\0" /* 5042 */ - "intersectButNotNever\0" /* 5055 */ - "PhpParser\\Node\\Expr\\Match_\0" /* 5076 */ - "condType\0" /* 5103 */ - "condNativeType\0" /* 5112 */ - "enterMatch\0" /* 5127 */ - "iteratee\0" /* 5138 */ - "iterateeType\0" /* 5147 */ - "nativeIterateeType\0" /* 5160 */ - "valueName\0" /* 5179 */ - "keyName\0" /* 5189 */ - "valueByRef\0" /* 5197 */ - "enterForeach\0" /* 5208 */ - "enterForeachKey\0" /* 5221 */ - "catchType\0" /* 5237 */ - "enterCatchType\0" /* 5247 */ - "isPlainWrite\0" /* 5262 */ - "enterExpressionAssign\0" /* 5275 */ - "exitExpressionAssign\0" /* 5297 */ - "isInExpressionAssign\0" /* 5318 */ - "isInWriteExpressionAssign\0" /* 5339 */ - "setAllowedUndefinedExpression\0" /* 5365 */ - "unsetAllowedUndefinedExpression\0" /* 5395 */ - "isUndefinedExpressionAllowed\0" /* 5427 */ - "certainty\0" /* 5456 */ - "intertwinedPropagatedFrom\0" /* 5466 */ - "assignVariable\0" /* 5492 */ - "overwriteExpression\0" /* 5507 */ - "scope\0" /* 5527 */ - "rootType\0" /* 5533 */ - "assignedExpr\0" /* 5542 */ - "rootVariableName\0" /* 5555 */ - "native\0" /* 5572 */ - "resolveIntertwinedAssignedType\0" /* 5579 */ - "dimFetch\0" /* 5610 */ - "PhpParser\\Node\\Expr\\ArrayDimFetch\0" /* 5619 */ - "isDimFetchPathReachable\0" /* 5653 */ - "unsetExpression\0" /* 5677 */ - "getStateType\0" /* 5693 */ - "getScopeStateType\0" /* 5706 */ - "getScopeStateNativeType\0" /* 5724 */ - "resolveScopeStateType\0" /* 5748 */ - "specifyExpressionType\0" /* 5770 */ - "openSpecificationScope\0" /* 5792 */ - "isSpecifyExpressionTypeNoop\0" /* 5815 */ - "specifyExpressionTypeInPlace\0" /* 5843 */ - "assignExpression\0" /* 5872 */ - "fetchedOnType\0" /* 5889 */ - "assignInitializedProperty\0" /* 5903 */ - "expressionToInvalidate\0" /* 5929 */ - "requireMoreCharacters\0" /* 5952 */ - "invalidatingClass\0" /* 5974 */ - "keepPropertyFetches\0" /* 5992 */ - "invalidateExpression\0" /* 6012 */ - "isPrivatePropertyOfDifferentClass\0" /* 6033 */ - "invalidateMethodsOnExpression\0" /* 6067 */ - "setExpressionCertaintyKeepingType\0" /* 6097 */ - "isComplexUnionType\0" /* 6131 */ - "addTypeToExpression\0" /* 6150 */ - "typeToRemove\0" /* 6170 */ - "removeTypeFromExpression\0" /* 6183 */ - "filterByTruthyValue\0" /* 6208 */ - "filterByFalseyValue\0" /* 6228 */ - "specifiedTypes\0" /* 6248 */ - "applySpecifiedTypes\0" /* 6263 */ - "specifiedExpressions\0" /* 6283 */ - "processConditionalExpressionsAfterSpecifying\0" /* 6304 */ - "getConditionalExpressions\0" /* 6349 */ - "conditionalExpressionHolders\0" /* 6375 */ - "addConditionalExpressions\0" /* 6404 */ - "exitFirstLevelStatements\0" /* 6430 */ - "isInFirstLevelStatement\0" /* 6455 */ - "otherScope\0" /* 6479 */ - "preserveVacuousConditionals\0" /* 6490 */ - "mergeWith\0" /* 6518 */ - "ours\0" /* 6528 */ - "theirs\0" /* 6533 */ - "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 */ + "getClosureBindScopeClassName\0" /* 2800 */ + "isClosureBindScopeClassAmbiguous\0" /* 2829 */ + "getClosureBindScopeCommonAncestor\0" /* 2862 */ + "getClosureBindScopeClassReflection\0" /* 2896 */ + "value\0" /* 2931 */ + "getTypeFromValue\0" /* 2937 */ + "hasExpressionType\0" /* 2954 */ + "getTrackedExpressionType\0" /* 2972 */ + "reflection\0" /* 2997 */ + "parameter\0" /* 3008 */ + "PHPStan\\Reflection\\ParameterReflection\0" /* 3018 */ + "rememberTypes\0" /* 3057 */ + "pushInFunctionCall\0" /* 3071 */ + "popInFunctionCall\0" /* 3090 */ + "className\0" /* 3108 */ + "isInClassExists\0" /* 3118 */ + "getFunctionCallStack\0" /* 3134 */ + "getFunctionCallStackWithParameters\0" /* 3155 */ + "functionName\0" /* 3190 */ + "isInFunctionExists\0" /* 3203 */ + "classReflection\0" /* 3222 */ + "enterClass\0" /* 3238 */ + "traitReflection\0" /* 3249 */ + "enterTrait\0" /* 3265 */ + "classMethod\0" /* 3276 */ + "PhpParser\\Node\\Stmt\\ClassMethod\0" /* 3288 */ + "templateTypeMap\0" /* 3320 */ + "PHPStan\\Type\\Generic\\TemplateTypeMap\0" /* 3336 */ + "phpDocParameterTypes\0" /* 3373 */ + "phpDocReturnType\0" /* 3394 */ + "throwType\0" /* 3411 */ + "deprecatedDescription\0" /* 3421 */ + "isDeprecated\0" /* 3443 */ + "isInternal\0" /* 3456 */ + "isFinal\0" /* 3467 */ + "isPure\0" /* 3475 */ + "acceptsNamedArguments\0" /* 3482 */ + "asserts\0" /* 3504 */ + "PHPStan\\Reflection\\Assertions\0" /* 3512 */ + "selfOutType\0" /* 3542 */ + "phpDocComment\0" /* 3554 */ + "parameterOutTypes\0" /* 3568 */ + "immediatelyInvokedCallableParameters\0" /* 3586 */ + "phpDocClosureThisTypeParameters\0" /* 3623 */ + "isConstructor\0" /* 3655 */ + "resolvedPhpDocBlock\0" /* 3669 */ + "PHPStan\\PhpDoc\\ResolvedPhpDocBlock\0" /* 3689 */ + "phpDocPureUnlessCallableIsImpureParameters\0" /* 3724 */ + "phpDocPureUnlessParameterPassedParameters\0" /* 3767 */ + "enterClassMethod\0" /* 3809 */ + "hook\0" /* 3826 */ + "PhpParser\\Node\\PropertyHook\0" /* 3831 */ + "propertyName\0" /* 3859 */ + "nativePropertyTypeNode\0" /* 3872 */ + "PhpParser\\Node\\Identifier|PhpParser\\Node\\Name|PhpParser\\Node\\ComplexType\0" /* 3895 */ + "phpDocPropertyType\0" /* 3968 */ + "enterPropertyHook\0" /* 3987 */ + "type\0" /* 4005 */ + "transformStaticType\0" /* 4010 */ + "functionLike\0" /* 4030 */ + "PhpParser\\Node\\FunctionLike\0" /* 4043 */ + "getRealParameterTypes\0" /* 4071 */ + "getRealParameterDefaultValues\0" /* 4093 */ + "PhpParser\\Node\\Stmt\\ClassMethod|PhpParser\\Node\\Stmt\\Function_|PhpParser\\Node\\PropertyHook\0" /* 4123 */ + "getParameterAttributes\0" /* 4213 */ + "PhpParser\\Node\\Stmt\\Function_\0" /* 4236 */ + "pureUnlessCallableIsImpureParameters\0" /* 4266 */ + "pureUnlessParameterPassedParameters\0" /* 4303 */ + "enterFunction\0" /* 4339 */ + "functionReflection\0" /* 4353 */ + "preserveConstructorScope\0" /* 4372 */ + "enterFunctionLike\0" /* 4397 */ + "namespaceName\0" /* 4415 */ + "enterNamespace\0" /* 4429 */ + "thisType\0" /* 4444 */ + "nativeThisType\0" /* 4453 */ + "scopeClasses\0" /* 4468 */ + "enterClosureBind\0" /* 4481 */ + "originalScope\0" /* 4498 */ + "restoreOriginalScopeAfterClosureBind\0" /* 4512 */ + "restoreThisScope\0" /* 4549 */ + "restoreThis\0" /* 4566 */ + "enterClosureCall\0" /* 4578 */ + "classNames\0" /* 4595 */ + "closureBindScopeClassesOf\0" /* 4606 */ + "isInClosureBind\0" /* 4632 */ + "withClosureBindScopeClasses\0" /* 4648 */ + "closure\0" /* 4676 */ + "PhpParser\\Node\\Expr\\Closure\0" /* 4684 */ + "callableParameters\0" /* 4712 */ + "nativeCallableParameters\0" /* 4731 */ + "enterAnonymousFunction\0" /* 4756 */ + "enterAnonymousFunctionWithoutReflection\0" /* 4779 */ + "typeHolder\0" /* 4819 */ + "PHPStan\\Analyser\\ExpressionTypeHolder\0" /* 4830 */ + "expressionTypeIsUnchangeable\0" /* 4868 */ + "invalidateStaticExpressions\0" /* 4897 */ + "arrowFunction\0" /* 4925 */ + "PhpParser\\Node\\Expr\\ArrowFunction\0" /* 4939 */ + "enterArrowFunction\0" /* 4973 */ + "enterArrowFunctionWithoutReflection\0" /* 4992 */ + "PhpParser\\Node\\Param\0" /* 5028 */ + "isParameterValueNullable\0" /* 5049 */ + "isNullable\0" /* 5074 */ + "isVariadic\0" /* 5085 */ + "getFunctionType\0" /* 5096 */ + "index\0" /* 5112 */ + "getCallableParameterType\0" /* 5118 */ + "startIndex\0" /* 5143 */ + "buildVariadicArrayTypeFromCallableParameters\0" /* 5154 */ + "nativeType\0" /* 5199 */ + "inferredType\0" /* 5210 */ + "intersectButNotNever\0" /* 5223 */ + "PhpParser\\Node\\Expr\\Match_\0" /* 5244 */ + "condType\0" /* 5271 */ + "condNativeType\0" /* 5280 */ + "enterMatch\0" /* 5295 */ + "iteratee\0" /* 5306 */ + "iterateeType\0" /* 5315 */ + "nativeIterateeType\0" /* 5328 */ + "valueName\0" /* 5347 */ + "keyName\0" /* 5357 */ + "valueByRef\0" /* 5365 */ + "enterForeach\0" /* 5376 */ + "enterForeachKey\0" /* 5389 */ + "catchType\0" /* 5405 */ + "enterCatchType\0" /* 5415 */ + "isPlainWrite\0" /* 5430 */ + "enterExpressionAssign\0" /* 5443 */ + "exitExpressionAssign\0" /* 5465 */ + "isInExpressionAssign\0" /* 5486 */ + "isInWriteExpressionAssign\0" /* 5507 */ + "setAllowedUndefinedExpression\0" /* 5533 */ + "unsetAllowedUndefinedExpression\0" /* 5563 */ + "isUndefinedExpressionAllowed\0" /* 5595 */ + "certainty\0" /* 5624 */ + "intertwinedPropagatedFrom\0" /* 5634 */ + "assignVariable\0" /* 5660 */ + "overwriteExpression\0" /* 5675 */ + "scope\0" /* 5695 */ + "rootType\0" /* 5701 */ + "assignedExpr\0" /* 5710 */ + "rootVariableName\0" /* 5723 */ + "native\0" /* 5740 */ + "resolveIntertwinedAssignedType\0" /* 5747 */ + "dimFetch\0" /* 5778 */ + "PhpParser\\Node\\Expr\\ArrayDimFetch\0" /* 5787 */ + "isDimFetchPathReachable\0" /* 5821 */ + "unsetExpression\0" /* 5845 */ + "getStateType\0" /* 5861 */ + "getScopeStateType\0" /* 5874 */ + "getScopeStateNativeType\0" /* 5892 */ + "resolveScopeStateType\0" /* 5916 */ + "specifyExpressionType\0" /* 5938 */ + "openSpecificationScope\0" /* 5960 */ + "isSpecifyExpressionTypeNoop\0" /* 5983 */ + "specifyExpressionTypeInPlace\0" /* 6011 */ + "assignExpression\0" /* 6040 */ + "fetchedOnType\0" /* 6057 */ + "assignInitializedProperty\0" /* 6071 */ + "expressionToInvalidate\0" /* 6097 */ + "requireMoreCharacters\0" /* 6120 */ + "invalidatingClass\0" /* 6142 */ + "keepPropertyFetches\0" /* 6160 */ + "invalidateExpression\0" /* 6180 */ + "isPrivatePropertyOfDifferentClass\0" /* 6201 */ + "invalidateMethodsOnExpression\0" /* 6235 */ + "setExpressionCertaintyKeepingType\0" /* 6265 */ + "isComplexUnionType\0" /* 6299 */ + "addTypeToExpression\0" /* 6318 */ + "typeToRemove\0" /* 6338 */ + "removeTypeFromExpression\0" /* 6351 */ + "filterByTruthyValue\0" /* 6376 */ + "filterByFalseyValue\0" /* 6396 */ + "specifiedTypes\0" /* 6416 */ + "applySpecifiedTypes\0" /* 6431 */ + "specifiedExpressions\0" /* 6451 */ + "processConditionalExpressionsAfterSpecifying\0" /* 6472 */ + "getConditionalExpressions\0" /* 6517 */ + "conditionalExpressionHolders\0" /* 6543 */ + "addConditionalExpressions\0" /* 6572 */ + "exitFirstLevelStatements\0" /* 6598 */ + "isInFirstLevelStatement\0" /* 6623 */ + "otherScope\0" /* 6647 */ + "preserveVacuousConditionals\0" /* 6658 */ + "mergeWith\0" /* 6686 */ + "ours\0" /* 6696 */ + "theirs\0" /* 6701 */ + "collectAbsorbedClosures\0" /* 6708 */ + "mergeWithVariableState\0" /* 6732 */ + "differingExpressionKeys\0" /* 6755 */ + "ourExpressionTypes\0" /* 6779 */ + "theirExpressionTypes\0" /* 6798 */ + "withoutPreciseClassConstantFetches\0" /* 6819 */ + "currentConditionalExpressions\0" /* 6854 */ + "sourceConditionalExpressions\0" /* 6884 */ + "otherExpressionTypes\0" /* 6913 */ + "preserveVacuousConditionalExpressions\0" /* 6934 */ + "ourConditionalExpressions\0" /* 6972 */ + "theirConditionalExpressions\0" /* 6998 */ + "mergeSameGuardConditionalExpressions\0" /* 7026 */ + "newConditionalExpressions\0" /* 7063 */ + "existingConditionalExpressions\0" /* 7089 */ + "mergeConditionalExpressions\0" /* 7120 */ + "calledMethodScope\0" /* 7148 */ + "mergeInitializedProperties\0" /* 7166 */ + "finallyScope\0" /* 7193 */ + "originalFinallyScope\0" /* 7206 */ + "processFinallyScope\0" /* 7227 */ + "ourVariableTypeHolders\0" /* 7247 */ + "finallyVariableTypeHolders\0" /* 7270 */ + "originalVariableTypeHolders\0" /* 7297 */ + "processFinallyScopeVariableTypeHolders\0" /* 7325 */ + "closureScope\0" /* 7364 */ + "prevScope\0" /* 7377 */ + "byRefUses\0" /* 7387 */ + "processClosureScope\0" /* 7397 */ + "finalScope\0" /* 7417 */ + "processAlwaysIterableForeachScopeWithoutPollute\0" /* 7428 */ + "writableVariableNames\0" /* 7476 */ + "generalizeWith\0" /* 7498 */ + "generalizeWithVariableState\0" /* 7513 */ + "variableTypeHolders\0" /* 7541 */ + "otherVariableTypeHolders\0" /* 7561 */ + "generalizeVariableTypeHolders\0" /* 7586 */ + "flattenUnionForGeneralization\0" /* 7616 */ + "a\0" /* 7646 */ + "b\0" /* 7648 */ + "depth\0" /* 7650 */ + "generalizeType\0" /* 7656 */ + "getArrayDepth\0" /* 7671 */ + "equals\0" /* 7685 */ + "otherConditionalExpressions\0" /* 7692 */ + "compareConditionalExpressions\0" /* 7720 */ + "compareVariableTypeHolders\0" /* 7750 */ + "propertyReflection\0" /* 7777 */ + "PHPStan\\Reflection\\PropertyReflection\0" /* 7796 */ + "canAccessProperty\0" /* 7834 */ + "PHPStan\\Reflection\\ExtendedPropertyReflection\0" /* 7852 */ + "canReadProperty\0" /* 7898 */ + "canWriteProperty\0" /* 7914 */ + "methodReflection\0" /* 7931 */ + "PHPStan\\Reflection\\MethodReflection\0" /* 7948 */ + "canCallMethod\0" /* 7984 */ + "constantReflection\0" /* 7998 */ + "PHPStan\\Reflection\\ClassConstantReflection\0" /* 8017 */ + "canAccessConstant\0" /* 8060 */ + "classMemberReflection\0" /* 8078 */ + "PHPStan\\Reflection\\ClassMemberReflection\0" /* 8100 */ + "canAccessClassMember\0" /* 8141 */ + "debug\0" /* 8162 */ + "typeWithMethod\0" /* 8168 */ + "methodName\0" /* 8183 */ + "filterTypeWithMethod\0" /* 8194 */ + "PHPStan\\Reflection\\ExtendedMethodReflection\0" /* 8215 */ + "getMethodReflection\0" /* 8259 */ + "getNakedMethod\0" /* 8279 */ + "typeWithProperty\0" /* 8294 */ + "getPropertyReflection\0" /* 8311 */ + "getInstancePropertyReflection\0" /* 8333 */ + "getStaticPropertyReflection\0" /* 8363 */ + "typeWithConstant\0" /* 8391 */ + "constantName\0" /* 8408 */ + "getConstantReflection\0" /* 8421 */ + "constantType\0" /* 8443 */ + "getConstantExplicitTypeFromConfig\0" /* 8456 */ + "getConstantTypes\0" /* 8490 */ + "getGlobalConstantType\0" /* 8507 */ + "nameString\0" /* 8529 */ + "createGlobalConstantFetches\0" /* 8540 */ + "getNativeConstantTypes\0" /* 8568 */ + "getIterableKeyType\0" /* 8591 */ + "getIterableValueType\0" /* 8610 */ + "PHPStan\\Php\\PhpVersions\0" /* 8631 */ + "getPhpVersion\0" /* 8655 */ + "getConfiguredPhpVersionRange\0" /* 8669 */ + "isOverallPhpVersionRange\0" /* 8698 */ + "PhpParser\\Node\0" /* 8723 */ + "invokeNodeCallback\0" /* 8738 */ + "collectorType\0" /* 8757 */ + "data\0" /* 8771 */ + "emitCollectedData\0" /* 8776 */ + "extensionClass\0" /* 8794 */ + "trackValueDependency\0" /* 8809 */ + "file\0" /* 8830 */ + "trackFileDependency\0" /* 8835 */ + "directory\0" /* 8855 */ + "pattern\0" /* 8865 */ + "'*'\0" /* 8873 */ + "trackDirectoryDependency\0" /* 8877 */ + "trackClassDependency"; /* 8902 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 9), /* __construct $services */ reg::packed(40, 0, 53), /* __construct $scopeFactory */ @@ -632,165 +638,172 @@ inline constexpr reg::PackedArg args[] = { reg::packed(738, MAY_BE_STRING), /* resolveName return */ reg::packed(1520, 0, 1525), /* resolveTypeByName $name */ reg::packed(738, 0, 2751), /* resolveTypeByName return */ - reg::packed(2800, 0), /* getTypeFromValue $value */ + reg::packed(738, MAY_BE_NULL | MAY_BE_STRING), /* getClosureBindScopeClassName return */ + reg::packed(738, MAY_BE_BOOL), /* isClosureBindScopeClassAmbiguous return */ + reg::packed(1520, 0, 1525), /* getClosureBindScopeCommonAncestor $name */ + reg::packed(738, MAY_BE_NULL, 1065), /* getClosureBindScopeCommonAncestor return */ + reg::packed(738, MAY_BE_NULL, 1065), /* getClosureBindScopeClassReflection return */ + reg::packed(2931, 0), /* getTypeFromValue $value */ reg::packed(738, 0, 1424), /* getTypeFromValue return */ reg::packed(1707, 0, 1712), /* hasExpressionType $node */ reg::packed(738, 0, 1387), /* hasExpressionType return */ reg::packed(1707, 0, 1712), /* getTrackedExpressionType $node */ reg::packed(738, 0, 1424), /* getTrackedExpressionType return */ - reg::packed(2866, 0), /* pushInFunctionCall $reflection */ - reg::packed(2877, MAY_BE_NULL, 2887), /* pushInFunctionCall $parameter */ - reg::packed(2926, MAY_BE_BOOL), /* pushInFunctionCall $rememberTypes */ + reg::packed(2997, 0), /* pushInFunctionCall $reflection */ + reg::packed(3008, MAY_BE_NULL, 3018), /* pushInFunctionCall $parameter */ + reg::packed(3057, MAY_BE_BOOL), /* pushInFunctionCall $rememberTypes */ reg::packed(738, 0, 523), /* pushInFunctionCall return */ reg::packed(738, 0, 523), /* popInFunctionCall return */ - reg::packed(2977, MAY_BE_STRING), /* isInClassExists $className */ + reg::packed(3108, MAY_BE_STRING), /* isInClassExists $className */ reg::packed(738, MAY_BE_BOOL), /* isInClassExists return */ reg::packed(738, MAY_BE_ARRAY), /* getFunctionCallStack return */ reg::packed(738, MAY_BE_ARRAY), /* getFunctionCallStackWithParameters return */ - reg::packed(3059, MAY_BE_STRING), /* isInFunctionExists $functionName */ + reg::packed(3190, MAY_BE_STRING), /* isInFunctionExists $functionName */ reg::packed(738, MAY_BE_BOOL), /* isInFunctionExists return */ - reg::packed(3091, 0, 1065), /* enterClass $classReflection */ + reg::packed(3222, 0, 1065), /* enterClass $classReflection */ reg::packed(738, 0, 523), /* enterClass return */ - reg::packed(3118, 0, 1065), /* enterTrait $traitReflection */ + reg::packed(3249, 0, 1065), /* enterTrait $traitReflection */ reg::packed(738, 0, 523), /* enterTrait return */ - reg::packed(3145, 0, 3157), /* enterClassMethod $classMethod */ - reg::packed(3189, 0, 3205), /* enterClassMethod $templateTypeMap */ - reg::packed(3242, MAY_BE_ARRAY), /* enterClassMethod $phpDocParameterTypes */ - reg::packed(3263, MAY_BE_NULL, 1424), /* enterClassMethod $phpDocReturnType */ - reg::packed(3280, MAY_BE_NULL, 1424), /* enterClassMethod $throwType */ - reg::packed(3290, MAY_BE_NULL | MAY_BE_STRING), /* enterClassMethod $deprecatedDescription */ - reg::packed(3312, MAY_BE_BOOL), /* enterClassMethod $isDeprecated */ - reg::packed(3325, MAY_BE_BOOL), /* enterClassMethod $isInternal */ - reg::packed(3336, MAY_BE_BOOL), /* enterClassMethod $isFinal */ - reg::packed(3344, MAY_BE_NULL | MAY_BE_BOOL, reg::NoString, false, false, 222), /* enterClassMethod $isPure */ - reg::packed(3351, MAY_BE_BOOL, reg::NoString, false, false, 402), /* enterClassMethod $acceptsNamedArguments */ - reg::packed(3373, MAY_BE_NULL, 3381, false, false, 222), /* enterClassMethod $asserts */ - reg::packed(3411, MAY_BE_NULL, 1424, false, false, 222), /* enterClassMethod $selfOutType */ - reg::packed(3423, MAY_BE_NULL | MAY_BE_STRING, reg::NoString, false, false, 222), /* enterClassMethod $phpDocComment */ - reg::packed(3437, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterClassMethod $parameterOutTypes */ - reg::packed(3455, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterClassMethod $immediatelyInvokedCallableParameters */ - reg::packed(3492, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterClassMethod $phpDocClosureThisTypeParameters */ - reg::packed(3524, MAY_BE_BOOL, reg::NoString, false, false, 148), /* enterClassMethod $isConstructor */ - reg::packed(3538, MAY_BE_NULL, 3558, false, false, 222), /* enterClassMethod $resolvedPhpDocBlock */ - reg::packed(3593, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterClassMethod $phpDocPureUnlessCallableIsImpureParameters */ - reg::packed(3636, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterClassMethod $phpDocPureUnlessParameterPassedParameters */ + reg::packed(3276, 0, 3288), /* enterClassMethod $classMethod */ + reg::packed(3320, 0, 3336), /* enterClassMethod $templateTypeMap */ + reg::packed(3373, MAY_BE_ARRAY), /* enterClassMethod $phpDocParameterTypes */ + reg::packed(3394, MAY_BE_NULL, 1424), /* enterClassMethod $phpDocReturnType */ + reg::packed(3411, MAY_BE_NULL, 1424), /* enterClassMethod $throwType */ + reg::packed(3421, MAY_BE_NULL | MAY_BE_STRING), /* enterClassMethod $deprecatedDescription */ + reg::packed(3443, MAY_BE_BOOL), /* enterClassMethod $isDeprecated */ + reg::packed(3456, MAY_BE_BOOL), /* enterClassMethod $isInternal */ + reg::packed(3467, MAY_BE_BOOL), /* enterClassMethod $isFinal */ + reg::packed(3475, MAY_BE_NULL | MAY_BE_BOOL, reg::NoString, false, false, 222), /* enterClassMethod $isPure */ + reg::packed(3482, MAY_BE_BOOL, reg::NoString, false, false, 402), /* enterClassMethod $acceptsNamedArguments */ + reg::packed(3504, MAY_BE_NULL, 3512, false, false, 222), /* enterClassMethod $asserts */ + reg::packed(3542, MAY_BE_NULL, 1424, false, false, 222), /* enterClassMethod $selfOutType */ + reg::packed(3554, MAY_BE_NULL | MAY_BE_STRING, reg::NoString, false, false, 222), /* enterClassMethod $phpDocComment */ + reg::packed(3568, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterClassMethod $parameterOutTypes */ + reg::packed(3586, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterClassMethod $immediatelyInvokedCallableParameters */ + reg::packed(3623, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterClassMethod $phpDocClosureThisTypeParameters */ + reg::packed(3655, MAY_BE_BOOL, reg::NoString, false, false, 148), /* enterClassMethod $isConstructor */ + reg::packed(3669, MAY_BE_NULL, 3689, false, false, 222), /* enterClassMethod $resolvedPhpDocBlock */ + reg::packed(3724, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterClassMethod $phpDocPureUnlessCallableIsImpureParameters */ + reg::packed(3767, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterClassMethod $phpDocPureUnlessParameterPassedParameters */ reg::packed(738, 0, 523), /* enterClassMethod return */ - reg::packed(3695, 0, 3700), /* enterPropertyHook $hook */ - reg::packed(3728, MAY_BE_STRING), /* enterPropertyHook $propertyName */ - reg::packed(3741, MAY_BE_NULL, 3764), /* enterPropertyHook $nativePropertyTypeNode */ - reg::packed(3837, MAY_BE_NULL, 1424), /* enterPropertyHook $phpDocPropertyType */ - reg::packed(3242, MAY_BE_ARRAY), /* enterPropertyHook $phpDocParameterTypes */ - reg::packed(3280, MAY_BE_NULL, 1424), /* enterPropertyHook $throwType */ - reg::packed(3290, MAY_BE_NULL | MAY_BE_STRING), /* enterPropertyHook $deprecatedDescription */ - reg::packed(3312, MAY_BE_BOOL), /* enterPropertyHook $isDeprecated */ - reg::packed(3344, MAY_BE_NULL | MAY_BE_BOOL), /* enterPropertyHook $isPure */ - reg::packed(3423, MAY_BE_NULL | MAY_BE_STRING), /* enterPropertyHook $phpDocComment */ - reg::packed(3538, MAY_BE_NULL, 3558, false, false, 222), /* enterPropertyHook $resolvedPhpDocBlock */ + reg::packed(3826, 0, 3831), /* enterPropertyHook $hook */ + reg::packed(3859, MAY_BE_STRING), /* enterPropertyHook $propertyName */ + reg::packed(3872, MAY_BE_NULL, 3895), /* enterPropertyHook $nativePropertyTypeNode */ + reg::packed(3968, MAY_BE_NULL, 1424), /* enterPropertyHook $phpDocPropertyType */ + reg::packed(3373, MAY_BE_ARRAY), /* enterPropertyHook $phpDocParameterTypes */ + reg::packed(3411, MAY_BE_NULL, 1424), /* enterPropertyHook $throwType */ + reg::packed(3421, MAY_BE_NULL | MAY_BE_STRING), /* enterPropertyHook $deprecatedDescription */ + reg::packed(3443, MAY_BE_BOOL), /* enterPropertyHook $isDeprecated */ + reg::packed(3475, MAY_BE_NULL | MAY_BE_BOOL), /* enterPropertyHook $isPure */ + reg::packed(3554, MAY_BE_NULL | MAY_BE_STRING), /* enterPropertyHook $phpDocComment */ + reg::packed(3669, MAY_BE_NULL, 3689, false, false, 222), /* enterPropertyHook $resolvedPhpDocBlock */ reg::packed(738, 0, 523), /* enterPropertyHook return */ - reg::packed(3874, 0, 1424), /* transformStaticType $type */ + reg::packed(4005, 0, 1424), /* transformStaticType $type */ reg::packed(738, 0, 1424), /* transformStaticType return */ - reg::packed(3899, 0, 3912), /* getRealParameterTypes $functionLike */ + reg::packed(4030, 0, 4043), /* getRealParameterTypes $functionLike */ reg::packed(738, MAY_BE_ARRAY), /* getRealParameterTypes return */ - reg::packed(3899, 0, 3912), /* getRealParameterDefaultValues $functionLike */ + reg::packed(4030, 0, 4043), /* getRealParameterDefaultValues $functionLike */ reg::packed(738, MAY_BE_ARRAY), /* getRealParameterDefaultValues return */ - reg::packed(3899, 0, 3992), /* getParameterAttributes $functionLike */ + reg::packed(4030, 0, 4123), /* getParameterAttributes $functionLike */ reg::packed(738, MAY_BE_ARRAY), /* getParameterAttributes return */ - reg::packed(154, 0, 4105), /* enterFunction $function */ - reg::packed(3189, 0, 3205), /* enterFunction $templateTypeMap */ - reg::packed(3242, MAY_BE_ARRAY), /* enterFunction $phpDocParameterTypes */ - reg::packed(3263, MAY_BE_NULL, 1424), /* enterFunction $phpDocReturnType */ - reg::packed(3280, MAY_BE_NULL, 1424), /* enterFunction $throwType */ - reg::packed(3290, MAY_BE_NULL | MAY_BE_STRING), /* enterFunction $deprecatedDescription */ - reg::packed(3312, MAY_BE_BOOL), /* enterFunction $isDeprecated */ - reg::packed(3325, MAY_BE_BOOL), /* enterFunction $isInternal */ - reg::packed(3344, MAY_BE_NULL | MAY_BE_BOOL, reg::NoString, false, false, 222), /* enterFunction $isPure */ - reg::packed(3351, MAY_BE_BOOL, reg::NoString, false, false, 402), /* enterFunction $acceptsNamedArguments */ - reg::packed(3373, MAY_BE_NULL, 3381, false, false, 222), /* enterFunction $asserts */ - reg::packed(3423, MAY_BE_NULL | MAY_BE_STRING, reg::NoString, false, false, 222), /* enterFunction $phpDocComment */ - reg::packed(3437, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterFunction $parameterOutTypes */ - reg::packed(3455, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterFunction $immediatelyInvokedCallableParameters */ - reg::packed(3492, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterFunction $phpDocClosureThisTypeParameters */ - reg::packed(4135, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterFunction $pureUnlessCallableIsImpureParameters */ - reg::packed(4172, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterFunction $pureUnlessParameterPassedParameters */ + reg::packed(154, 0, 4236), /* enterFunction $function */ + reg::packed(3320, 0, 3336), /* enterFunction $templateTypeMap */ + reg::packed(3373, MAY_BE_ARRAY), /* enterFunction $phpDocParameterTypes */ + reg::packed(3394, MAY_BE_NULL, 1424), /* enterFunction $phpDocReturnType */ + reg::packed(3411, MAY_BE_NULL, 1424), /* enterFunction $throwType */ + reg::packed(3421, MAY_BE_NULL | MAY_BE_STRING), /* enterFunction $deprecatedDescription */ + reg::packed(3443, MAY_BE_BOOL), /* enterFunction $isDeprecated */ + reg::packed(3456, MAY_BE_BOOL), /* enterFunction $isInternal */ + reg::packed(3475, MAY_BE_NULL | MAY_BE_BOOL, reg::NoString, false, false, 222), /* enterFunction $isPure */ + reg::packed(3482, MAY_BE_BOOL, reg::NoString, false, false, 402), /* enterFunction $acceptsNamedArguments */ + reg::packed(3504, MAY_BE_NULL, 3512, false, false, 222), /* enterFunction $asserts */ + reg::packed(3554, MAY_BE_NULL | MAY_BE_STRING, reg::NoString, false, false, 222), /* enterFunction $phpDocComment */ + reg::packed(3568, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterFunction $parameterOutTypes */ + reg::packed(3586, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterFunction $immediatelyInvokedCallableParameters */ + reg::packed(3623, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterFunction $phpDocClosureThisTypeParameters */ + reg::packed(4266, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterFunction $pureUnlessCallableIsImpureParameters */ + reg::packed(4303, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* enterFunction $pureUnlessParameterPassedParameters */ reg::packed(738, 0, 523), /* enterFunction return */ - reg::packed(4222, 0, 163), /* enterFunctionLike $functionReflection */ - reg::packed(4241, MAY_BE_BOOL), /* enterFunctionLike $preserveConstructorScope */ + reg::packed(4353, 0, 163), /* enterFunctionLike $functionReflection */ + reg::packed(4372, MAY_BE_BOOL), /* enterFunctionLike $preserveConstructorScope */ reg::packed(738, 0, 523), /* enterFunctionLike return */ - reg::packed(4284, MAY_BE_STRING), /* enterNamespace $namespaceName */ + reg::packed(4415, MAY_BE_STRING), /* enterNamespace $namespaceName */ reg::packed(738, 0, 523), /* enterNamespace return */ - reg::packed(4313, MAY_BE_NULL, 1424), /* enterClosureBind $thisType */ - reg::packed(4322, MAY_BE_NULL, 1424), /* enterClosureBind $nativeThisType */ - reg::packed(4337, MAY_BE_ARRAY), /* enterClosureBind $scopeClasses */ + reg::packed(4444, MAY_BE_NULL, 1424), /* enterClosureBind $thisType */ + reg::packed(4453, MAY_BE_NULL, 1424), /* enterClosureBind $nativeThisType */ + reg::packed(4468, MAY_BE_ARRAY), /* enterClosureBind $scopeClasses */ reg::packed(738, 0, 523), /* enterClosureBind return */ - reg::packed(4367, 0, 523), /* restoreOriginalScopeAfterClosureBind $originalScope */ + reg::packed(4498, 0, 523), /* restoreOriginalScopeAfterClosureBind $originalScope */ reg::packed(738, 0, 523), /* restoreOriginalScopeAfterClosureBind return */ - reg::packed(4418, 0, 523), /* restoreThis $restoreThisScope */ + reg::packed(4549, 0, 523), /* restoreThis $restoreThisScope */ reg::packed(738, 0, 523), /* restoreThis return */ - reg::packed(4313, 0, 1424), /* enterClosureCall $thisType */ - reg::packed(4322, 0, 1424), /* enterClosureCall $nativeThisType */ + reg::packed(4444, 0, 1424), /* enterClosureCall $thisType */ + reg::packed(4453, 0, 1424), /* enterClosureCall $nativeThisType */ reg::packed(738, 0, 523), /* enterClosureCall return */ + reg::packed(4595, MAY_BE_ARRAY), /* closureBindScopeClassesOf $classNames */ + reg::packed(738, MAY_BE_ARRAY), /* closureBindScopeClassesOf return */ reg::packed(738, MAY_BE_BOOL), /* isInClosureBind return */ - reg::packed(4337, MAY_BE_ARRAY), /* withClosureBindScopeClasses $scopeClasses */ + reg::packed(4468, MAY_BE_ARRAY), /* withClosureBindScopeClasses $scopeClasses */ reg::packed(738, 0, 523), /* withClosureBindScopeClasses return */ - reg::packed(4508, 0, 4516), /* enterAnonymousFunction $closure */ - reg::packed(4544, MAY_BE_NULL | MAY_BE_ARRAY), /* enterAnonymousFunction $callableParameters */ - reg::packed(4563, MAY_BE_NULL | MAY_BE_ARRAY, reg::NoString, false, false, 222), /* enterAnonymousFunction $nativeCallableParameters */ + reg::packed(4676, 0, 4684), /* enterAnonymousFunction $closure */ + reg::packed(4712, MAY_BE_NULL | MAY_BE_ARRAY), /* enterAnonymousFunction $callableParameters */ + reg::packed(4731, MAY_BE_NULL | MAY_BE_ARRAY, reg::NoString, false, false, 222), /* enterAnonymousFunction $nativeCallableParameters */ reg::packed(738, 0, 523), /* enterAnonymousFunction return */ - reg::packed(4508, 0, 4516), /* enterAnonymousFunctionWithoutReflection $closure */ - reg::packed(4544, MAY_BE_NULL | MAY_BE_ARRAY), /* enterAnonymousFunctionWithoutReflection $callableParameters */ - reg::packed(4563, MAY_BE_NULL | MAY_BE_ARRAY), /* enterAnonymousFunctionWithoutReflection $nativeCallableParameters */ + reg::packed(4676, 0, 4684), /* enterAnonymousFunctionWithoutReflection $closure */ + reg::packed(4712, MAY_BE_NULL | MAY_BE_ARRAY), /* enterAnonymousFunctionWithoutReflection $callableParameters */ + reg::packed(4731, MAY_BE_NULL | MAY_BE_ARRAY), /* enterAnonymousFunctionWithoutReflection $nativeCallableParameters */ reg::packed(738, 0, 523), /* enterAnonymousFunctionWithoutReflection return */ - reg::packed(4651, 0, 4662), /* expressionTypeIsUnchangeable $typeHolder */ + reg::packed(4819, 0, 4830), /* expressionTypeIsUnchangeable $typeHolder */ reg::packed(738, MAY_BE_BOOL), /* expressionTypeIsUnchangeable return */ reg::packed(237, MAY_BE_ARRAY), /* invalidateStaticExpressions $expressionTypes */ reg::packed(738, MAY_BE_ARRAY), /* invalidateStaticExpressions return */ - reg::packed(4757, 0, 4771), /* enterArrowFunction $arrowFunction */ - reg::packed(4544, MAY_BE_NULL | MAY_BE_ARRAY), /* enterArrowFunction $callableParameters */ - reg::packed(4563, MAY_BE_NULL | MAY_BE_ARRAY, reg::NoString, false, false, 222), /* enterArrowFunction $nativeCallableParameters */ + reg::packed(4925, 0, 4939), /* enterArrowFunction $arrowFunction */ + reg::packed(4712, MAY_BE_NULL | MAY_BE_ARRAY), /* enterArrowFunction $callableParameters */ + reg::packed(4731, MAY_BE_NULL | MAY_BE_ARRAY, reg::NoString, false, false, 222), /* enterArrowFunction $nativeCallableParameters */ reg::packed(738, 0, 523), /* enterArrowFunction return */ - reg::packed(4757, 0, 4771), /* enterArrowFunctionWithoutReflection $arrowFunction */ - reg::packed(4544, MAY_BE_NULL | MAY_BE_ARRAY), /* enterArrowFunctionWithoutReflection $callableParameters */ - reg::packed(4563, MAY_BE_NULL | MAY_BE_ARRAY), /* enterArrowFunctionWithoutReflection $nativeCallableParameters */ + reg::packed(4925, 0, 4939), /* enterArrowFunctionWithoutReflection $arrowFunction */ + reg::packed(4712, MAY_BE_NULL | MAY_BE_ARRAY), /* enterArrowFunctionWithoutReflection $callableParameters */ + reg::packed(4731, MAY_BE_NULL | MAY_BE_ARRAY), /* enterArrowFunctionWithoutReflection $nativeCallableParameters */ reg::packed(738, 0, 523), /* enterArrowFunctionWithoutReflection return */ - reg::packed(2877, 0, 4860), /* isParameterValueNullable $parameter */ + reg::packed(3008, 0, 5028), /* isParameterValueNullable $parameter */ reg::packed(738, MAY_BE_BOOL), /* isParameterValueNullable return */ - reg::packed(3874, 0), /* getFunctionType $type */ - reg::packed(4906, MAY_BE_BOOL), /* getFunctionType $isNullable */ - reg::packed(4917, MAY_BE_BOOL), /* getFunctionType $isVariadic */ + reg::packed(4005, 0), /* getFunctionType $type */ + reg::packed(5074, MAY_BE_BOOL), /* getFunctionType $isNullable */ + reg::packed(5085, MAY_BE_BOOL), /* getFunctionType $isVariadic */ reg::packed(738, 0, 1424), /* getFunctionType return */ - reg::packed(2877, 0, 4860), /* getCallableParameterType $parameter */ - reg::packed(4544, MAY_BE_ARRAY), /* getCallableParameterType $callableParameters */ - reg::packed(4944, MAY_BE_LONG), /* getCallableParameterType $index */ + reg::packed(3008, 0, 5028), /* getCallableParameterType $parameter */ + reg::packed(4712, MAY_BE_ARRAY), /* getCallableParameterType $callableParameters */ + reg::packed(5112, MAY_BE_LONG), /* getCallableParameterType $index */ reg::packed(738, 0, 1424), /* getCallableParameterType return */ - reg::packed(4544, MAY_BE_ARRAY), /* buildVariadicArrayTypeFromCallableParameters $callableParameters */ - reg::packed(4975, MAY_BE_LONG), /* buildVariadicArrayTypeFromCallableParameters $startIndex */ + reg::packed(4712, MAY_BE_ARRAY), /* buildVariadicArrayTypeFromCallableParameters $callableParameters */ + reg::packed(5143, MAY_BE_LONG), /* buildVariadicArrayTypeFromCallableParameters $startIndex */ reg::packed(738, 0, 1424), /* buildVariadicArrayTypeFromCallableParameters return */ - reg::packed(5031, 0, 1424), /* intersectButNotNever $nativeType */ - reg::packed(5042, 0, 1424), /* intersectButNotNever $inferredType */ + reg::packed(5199, 0, 1424), /* intersectButNotNever $nativeType */ + reg::packed(5210, 0, 1424), /* intersectButNotNever $inferredType */ reg::packed(738, 0, 1424), /* intersectButNotNever return */ - reg::packed(966, 0, 5076), /* enterMatch $expr */ - reg::packed(5103, 0, 1424), /* enterMatch $condType */ - reg::packed(5112, 0, 1424), /* enterMatch $condNativeType */ + reg::packed(966, 0, 5244), /* enterMatch $expr */ + reg::packed(5271, 0, 1424), /* enterMatch $condType */ + reg::packed(5280, 0, 1424), /* enterMatch $condNativeType */ reg::packed(738, 0, 523), /* enterMatch return */ - reg::packed(4367, 0, 523), /* enterForeach $originalScope */ - reg::packed(5138, 0, 1712), /* enterForeach $iteratee */ - reg::packed(5147, 0, 1424), /* enterForeach $iterateeType */ - reg::packed(5160, 0, 1424), /* enterForeach $nativeIterateeType */ - reg::packed(5179, MAY_BE_STRING), /* enterForeach $valueName */ - reg::packed(5189, MAY_BE_NULL | MAY_BE_STRING), /* enterForeach $keyName */ - reg::packed(5197, MAY_BE_BOOL), /* enterForeach $valueByRef */ + reg::packed(4498, 0, 523), /* enterForeach $originalScope */ + reg::packed(5306, 0, 1712), /* enterForeach $iteratee */ + reg::packed(5315, 0, 1424), /* enterForeach $iterateeType */ + reg::packed(5328, 0, 1424), /* enterForeach $nativeIterateeType */ + reg::packed(5347, MAY_BE_STRING), /* enterForeach $valueName */ + reg::packed(5357, MAY_BE_NULL | MAY_BE_STRING), /* enterForeach $keyName */ + reg::packed(5365, MAY_BE_BOOL), /* enterForeach $valueByRef */ reg::packed(738, 0, 523), /* enterForeach return */ - reg::packed(4367, 0, 523), /* enterForeachKey $originalScope */ - reg::packed(5138, 0, 1712), /* enterForeachKey $iteratee */ - reg::packed(5147, 0, 1424), /* enterForeachKey $iterateeType */ - reg::packed(5160, 0, 1424), /* enterForeachKey $nativeIterateeType */ - reg::packed(5189, MAY_BE_STRING), /* enterForeachKey $keyName */ + reg::packed(4498, 0, 523), /* enterForeachKey $originalScope */ + reg::packed(5306, 0, 1712), /* enterForeachKey $iteratee */ + reg::packed(5315, 0, 1424), /* enterForeachKey $iterateeType */ + reg::packed(5328, 0, 1424), /* enterForeachKey $nativeIterateeType */ + reg::packed(5357, MAY_BE_STRING), /* enterForeachKey $keyName */ reg::packed(738, 0, 523), /* enterForeachKey return */ - reg::packed(5237, 0, 1424), /* enterCatchType $catchType */ + reg::packed(5405, 0, 1424), /* enterCatchType $catchType */ reg::packed(1374, MAY_BE_NULL | MAY_BE_STRING), /* enterCatchType $variableName */ reg::packed(738, 0, 523), /* enterCatchType return */ reg::packed(966, 0, 1712), /* enterExpressionAssign $expr */ - reg::packed(5262, MAY_BE_BOOL, reg::NoString, false, false, 402), /* enterExpressionAssign $isPlainWrite */ + reg::packed(5430, MAY_BE_BOOL, reg::NoString, false, false, 402), /* enterExpressionAssign $isPlainWrite */ reg::packed(738, 0, 523), /* enterExpressionAssign return */ reg::packed(966, 0, 1712), /* exitExpressionAssign $expr */ reg::packed(738, 0, 523), /* exitExpressionAssign return */ @@ -805,23 +818,23 @@ inline constexpr reg::PackedArg args[] = { reg::packed(966, 0, 1712), /* isUndefinedExpressionAllowed $expr */ reg::packed(738, MAY_BE_BOOL), /* isUndefinedExpressionAllowed return */ reg::packed(1374, MAY_BE_STRING), /* assignVariable $variableName */ - reg::packed(3874, 0, 1424), /* assignVariable $type */ - reg::packed(5031, 0, 1424), /* assignVariable $nativeType */ - reg::packed(5456, 0, 1387), /* assignVariable $certainty */ - reg::packed(5466, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* assignVariable $intertwinedPropagatedFrom */ + reg::packed(4005, 0, 1424), /* assignVariable $type */ + reg::packed(5199, 0, 1424), /* assignVariable $nativeType */ + reg::packed(5624, 0, 1387), /* assignVariable $certainty */ + reg::packed(5634, MAY_BE_ARRAY, reg::NoString, false, false, 253), /* assignVariable $intertwinedPropagatedFrom */ reg::packed(738, 0, 523), /* assignVariable return */ reg::packed(966, 0, 1712), /* overwriteExpression $expr */ - reg::packed(3874, 0, 1424), /* overwriteExpression $type */ - reg::packed(5031, 0, 1424), /* overwriteExpression $nativeType */ + reg::packed(4005, 0, 1424), /* overwriteExpression $type */ + reg::packed(5199, 0, 1424), /* overwriteExpression $nativeType */ reg::packed(738, 0, 523), /* overwriteExpression return */ - reg::packed(5527, 0, 523), /* resolveIntertwinedAssignedType $scope */ - reg::packed(5533, 0, 1424), /* resolveIntertwinedAssignedType $rootType */ - reg::packed(5542, 0, 1712), /* resolveIntertwinedAssignedType $assignedExpr */ - reg::packed(5555, MAY_BE_STRING), /* resolveIntertwinedAssignedType $rootVariableName */ - reg::packed(5572, MAY_BE_BOOL), /* resolveIntertwinedAssignedType $native */ + reg::packed(5695, 0, 523), /* resolveIntertwinedAssignedType $scope */ + reg::packed(5701, 0, 1424), /* resolveIntertwinedAssignedType $rootType */ + reg::packed(5710, 0, 1712), /* resolveIntertwinedAssignedType $assignedExpr */ + reg::packed(5723, MAY_BE_STRING), /* resolveIntertwinedAssignedType $rootVariableName */ + reg::packed(5740, MAY_BE_BOOL), /* resolveIntertwinedAssignedType $native */ reg::packed(738, 0, 1424), /* resolveIntertwinedAssignedType return */ - reg::packed(5527, 0, 523), /* isDimFetchPathReachable $scope */ - reg::packed(5610, 0, 5619), /* isDimFetchPathReachable $dimFetch */ + reg::packed(5695, 0, 523), /* isDimFetchPathReachable $scope */ + reg::packed(5778, 0, 5787), /* isDimFetchPathReachable $dimFetch */ reg::packed(738, MAY_BE_BOOL), /* isDimFetchPathReachable return */ reg::packed(966, 0, 1712), /* unsetExpression $expr */ reg::packed(738, 0, 523), /* unsetExpression return */ @@ -832,196 +845,196 @@ inline constexpr reg::PackedArg args[] = { reg::packed(966, 0, 1712), /* getScopeStateNativeType $expr */ reg::packed(738, 0, 1424), /* getScopeStateNativeType return */ reg::packed(966, 0, 1712), /* resolveScopeStateType $expr */ - reg::packed(5572, MAY_BE_BOOL), /* resolveScopeStateType $native */ + reg::packed(5740, MAY_BE_BOOL), /* resolveScopeStateType $native */ reg::packed(738, 0, 1424), /* resolveScopeStateType return */ reg::packed(966, 0, 1712), /* specifyExpressionType $expr */ - reg::packed(3874, 0, 1424), /* specifyExpressionType $type */ - reg::packed(5031, 0, 1424), /* specifyExpressionType $nativeType */ - reg::packed(5456, 0, 1387), /* specifyExpressionType $certainty */ + reg::packed(4005, 0, 1424), /* specifyExpressionType $type */ + reg::packed(5199, 0, 1424), /* specifyExpressionType $nativeType */ + reg::packed(5624, 0, 1387), /* specifyExpressionType $certainty */ reg::packed(738, 0, 523), /* specifyExpressionType return */ reg::packed(738, 0, 523), /* openSpecificationScope return */ reg::packed(966, 0, 1712), /* isSpecifyExpressionTypeNoop $expr */ - reg::packed(3874, 0, 1424), /* isSpecifyExpressionTypeNoop $type */ + reg::packed(4005, 0, 1424), /* isSpecifyExpressionTypeNoop $type */ reg::packed(738, MAY_BE_BOOL), /* isSpecifyExpressionTypeNoop return */ reg::packed(966, 0, 1712), /* specifyExpressionTypeInPlace $expr */ - reg::packed(3874, 0, 1424), /* specifyExpressionTypeInPlace $type */ - reg::packed(5031, 0, 1424), /* specifyExpressionTypeInPlace $nativeType */ - reg::packed(5456, 0, 1387), /* specifyExpressionTypeInPlace $certainty */ + reg::packed(4005, 0, 1424), /* specifyExpressionTypeInPlace $type */ + reg::packed(5199, 0, 1424), /* specifyExpressionTypeInPlace $nativeType */ + reg::packed(5624, 0, 1387), /* specifyExpressionTypeInPlace $certainty */ reg::packed(738, MAY_BE_VOID), /* specifyExpressionTypeInPlace return */ reg::packed(966, 0, 1712), /* assignExpression $expr */ - reg::packed(3874, 0, 1424), /* assignExpression $type */ - reg::packed(5031, 0, 1424), /* assignExpression $nativeType */ + reg::packed(4005, 0, 1424), /* assignExpression $type */ + reg::packed(5199, 0, 1424), /* assignExpression $nativeType */ reg::packed(738, 0, 523), /* assignExpression return */ - reg::packed(5889, 0, 1424), /* assignInitializedProperty $fetchedOnType */ - reg::packed(3728, MAY_BE_STRING), /* assignInitializedProperty $propertyName */ + reg::packed(6057, 0, 1424), /* assignInitializedProperty $fetchedOnType */ + reg::packed(3859, MAY_BE_STRING), /* assignInitializedProperty $propertyName */ reg::packed(738, 0, 523), /* assignInitializedProperty return */ - reg::packed(5929, 0, 1712), /* invalidateExpression $expressionToInvalidate */ - reg::packed(5952, MAY_BE_BOOL, reg::NoString, false, false, 148), /* invalidateExpression $requireMoreCharacters */ - reg::packed(5974, MAY_BE_NULL, 1065, false, false, 222), /* invalidateExpression $invalidatingClass */ - reg::packed(5992, MAY_BE_BOOL, reg::NoString, false, false, 148), /* invalidateExpression $keepPropertyFetches */ + reg::packed(6097, 0, 1712), /* invalidateExpression $expressionToInvalidate */ + reg::packed(6120, MAY_BE_BOOL, reg::NoString, false, false, 148), /* invalidateExpression $requireMoreCharacters */ + reg::packed(6142, MAY_BE_NULL, 1065, false, false, 222), /* invalidateExpression $invalidatingClass */ + reg::packed(6160, MAY_BE_BOOL, reg::NoString, false, false, 148), /* invalidateExpression $keepPropertyFetches */ reg::packed(738, 0, 523), /* invalidateExpression return */ reg::packed(966, 0, 1712), /* isPrivatePropertyOfDifferentClass $expr */ - reg::packed(5974, 0, 1065), /* isPrivatePropertyOfDifferentClass $invalidatingClass */ + reg::packed(6142, 0, 1065), /* isPrivatePropertyOfDifferentClass $invalidatingClass */ reg::packed(738, MAY_BE_BOOL), /* isPrivatePropertyOfDifferentClass return */ - reg::packed(5929, 0, 1712), /* invalidateMethodsOnExpression $expressionToInvalidate */ + reg::packed(6097, 0, 1712), /* invalidateMethodsOnExpression $expressionToInvalidate */ reg::packed(738, 0, 523), /* invalidateMethodsOnExpression return */ reg::packed(966, 0, 1712), /* setExpressionCertaintyKeepingType $expr */ - reg::packed(5456, 0, 1387), /* setExpressionCertaintyKeepingType $certainty */ + reg::packed(5624, 0, 1387), /* setExpressionCertaintyKeepingType $certainty */ reg::packed(738, 0, 523), /* setExpressionCertaintyKeepingType return */ - reg::packed(3874, 0, 1424), /* isComplexUnionType $type */ + reg::packed(4005, 0, 1424), /* isComplexUnionType $type */ reg::packed(738, MAY_BE_BOOL), /* isComplexUnionType return */ reg::packed(966, 0, 1712), /* addTypeToExpression $expr */ - reg::packed(3874, 0, 1424), /* addTypeToExpression $type */ + reg::packed(4005, 0, 1424), /* addTypeToExpression $type */ reg::packed(738, 0, 523), /* addTypeToExpression return */ reg::packed(966, 0, 1712), /* removeTypeFromExpression $expr */ - reg::packed(6170, 0, 1424), /* removeTypeFromExpression $typeToRemove */ + reg::packed(6338, 0, 1424), /* removeTypeFromExpression $typeToRemove */ reg::packed(738, 0, 523), /* removeTypeFromExpression return */ reg::packed(966, 0, 1712), /* filterByTruthyValue $expr */ reg::packed(738, 0, 523), /* filterByTruthyValue return */ reg::packed(966, 0, 1712), /* filterByFalseyValue $expr */ reg::packed(738, 0, 523), /* filterByFalseyValue return */ - reg::packed(6248, 0, 2035), /* applySpecifiedTypes $specifiedTypes */ + reg::packed(6416, 0, 2035), /* applySpecifiedTypes $specifiedTypes */ reg::packed(738, 0, 523), /* applySpecifiedTypes return */ - reg::packed(6283, MAY_BE_ARRAY), /* processConditionalExpressionsAfterSpecifying $specifiedExpressions */ + reg::packed(6451, MAY_BE_ARRAY), /* processConditionalExpressionsAfterSpecifying $specifiedExpressions */ reg::packed(738, 0, 523), /* processConditionalExpressionsAfterSpecifying return */ reg::packed(738, MAY_BE_ARRAY), /* getConditionalExpressions return */ reg::packed(1883, MAY_BE_STRING), /* addConditionalExpressions $exprString */ - reg::packed(6375, MAY_BE_ARRAY), /* addConditionalExpressions $conditionalExpressionHolders */ + reg::packed(6543, MAY_BE_ARRAY), /* addConditionalExpressions $conditionalExpressionHolders */ reg::packed(738, 0, 523), /* addConditionalExpressions return */ reg::packed(738, 0, 523), /* exitFirstLevelStatements return */ reg::packed(738, MAY_BE_BOOL), /* isInFirstLevelStatement return */ - reg::packed(6479, MAY_BE_NULL, 523), /* mergeWith $otherScope */ - reg::packed(6490, MAY_BE_BOOL, reg::NoString, false, false, 148), /* mergeWith $preserveVacuousConditionals */ + reg::packed(6647, MAY_BE_NULL, 523), /* mergeWith $otherScope */ + reg::packed(6658, MAY_BE_BOOL, reg::NoString, false, false, 148), /* mergeWith $preserveVacuousConditionals */ reg::packed(738, 0, 523), /* mergeWith return */ - reg::packed(6528, 0, 523), /* collectAbsorbedClosures $ours */ - reg::packed(6533, 0, 523), /* collectAbsorbedClosures $theirs */ + reg::packed(6696, 0, 523), /* collectAbsorbedClosures $ours */ + reg::packed(6701, 0, 523), /* collectAbsorbedClosures $theirs */ reg::packed(738, MAY_BE_NULL, 672), /* collectAbsorbedClosures return */ - reg::packed(6479, MAY_BE_NULL, 523), /* mergeWithVariableState $otherScope */ - reg::packed(6490, MAY_BE_BOOL, reg::NoString, false, false, 148), /* mergeWithVariableState $preserveVacuousConditionals */ + reg::packed(6647, MAY_BE_NULL, 523), /* mergeWithVariableState $otherScope */ + reg::packed(6658, MAY_BE_BOOL, reg::NoString, false, false, 148), /* mergeWithVariableState $preserveVacuousConditionals */ reg::packed(738, 0, 523), /* mergeWithVariableState 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(6755, MAY_BE_ARRAY), /* withoutPreciseClassConstantFetches $differingExpressionKeys */ + reg::packed(6779, MAY_BE_ARRAY), /* withoutPreciseClassConstantFetches $ourExpressionTypes */ + reg::packed(6798, 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(6854, MAY_BE_ARRAY), /* preserveVacuousConditionalExpressions $currentConditionalExpressions */ + reg::packed(6884, MAY_BE_ARRAY), /* preserveVacuousConditionalExpressions $sourceConditionalExpressions */ + reg::packed(6913, 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(6854, MAY_BE_ARRAY), /* mergeSameGuardConditionalExpressions $currentConditionalExpressions */ + reg::packed(6972, MAY_BE_ARRAY), /* mergeSameGuardConditionalExpressions $ourConditionalExpressions */ + reg::packed(6998, 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(7063, MAY_BE_ARRAY), /* mergeConditionalExpressions $newConditionalExpressions */ + reg::packed(7089, MAY_BE_ARRAY), /* mergeConditionalExpressions $existingConditionalExpressions */ reg::packed(738, MAY_BE_ARRAY), /* mergeConditionalExpressions return */ - reg::packed(6980, 0, 523), /* mergeInitializedProperties $calledMethodScope */ + reg::packed(7148, 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(7193, 0, 523), /* processFinallyScope $finallyScope */ + reg::packed(7206, 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(7247, MAY_BE_ARRAY), /* processFinallyScopeVariableTypeHolders $ourVariableTypeHolders */ + reg::packed(7270, MAY_BE_ARRAY), /* processFinallyScopeVariableTypeHolders $finallyVariableTypeHolders */ + reg::packed(7297, 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(7364, 0, 523), /* processClosureScope $closureScope */ + reg::packed(7377, MAY_BE_NULL, 523), /* processClosureScope $prevScope */ + reg::packed(7387, MAY_BE_ARRAY), /* processClosureScope $byRefUses */ reg::packed(738, 0, 523), /* processClosureScope return */ - reg::packed(7249, 0, 523), /* processAlwaysIterableForeachScopeWithoutPollute $finalScope */ + reg::packed(7417, 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(6647, 0, 523), /* generalizeWith $otherScope */ + reg::packed(7476, 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(6647, 0, 523), /* generalizeWithVariableState $otherScope */ + reg::packed(7476, 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(7541, MAY_BE_ARRAY), /* generalizeVariableTypeHolders $variableTypeHolders */ + reg::packed(7561, MAY_BE_ARRAY), /* generalizeVariableTypeHolders $otherVariableTypeHolders */ + reg::packed(7476, 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(4005, 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(7646, 0, 1424), /* generalizeType $a */ + reg::packed(7648, 0, 1424), /* generalizeType $b */ + reg::packed(7650, MAY_BE_LONG), /* generalizeType $depth */ reg::packed(738, 0, 1424), /* generalizeType return */ - reg::packed(3874, 0, 1424), /* getArrayDepth $type */ + reg::packed(4005, 0, 1424), /* getArrayDepth $type */ reg::packed(738, MAY_BE_LONG), /* getArrayDepth return */ - reg::packed(6479, 0, 523), /* equals $otherScope */ + reg::packed(6647, 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(7692, 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(7541, MAY_BE_ARRAY), /* compareVariableTypeHolders $variableTypeHolders */ + reg::packed(7561, MAY_BE_ARRAY), /* compareVariableTypeHolders $otherVariableTypeHolders */ reg::packed(738, MAY_BE_BOOL), /* compareVariableTypeHolders return */ - reg::packed(7609, 0, 7628), /* canAccessProperty $propertyReflection */ + reg::packed(7777, 0, 7796), /* canAccessProperty $propertyReflection */ reg::packed(738, MAY_BE_BOOL), /* canAccessProperty return */ - reg::packed(7609, 0, 7684), /* canReadProperty $propertyReflection */ + reg::packed(7777, 0, 7852), /* canReadProperty $propertyReflection */ reg::packed(738, MAY_BE_BOOL), /* canReadProperty return */ - reg::packed(7609, 0, 7684), /* canWriteProperty $propertyReflection */ + reg::packed(7777, 0, 7852), /* canWriteProperty $propertyReflection */ reg::packed(738, MAY_BE_BOOL), /* canWriteProperty return */ - reg::packed(7763, 0, 7780), /* canCallMethod $methodReflection */ + reg::packed(7931, 0, 7948), /* canCallMethod $methodReflection */ reg::packed(738, MAY_BE_BOOL), /* canCallMethod return */ - reg::packed(7830, 0, 7849), /* canAccessConstant $constantReflection */ + reg::packed(7998, 0, 8017), /* canAccessConstant $constantReflection */ reg::packed(738, MAY_BE_BOOL), /* canAccessConstant return */ - reg::packed(7910, 0, 7932), /* canAccessClassMember $classMemberReflection */ + reg::packed(8078, 0, 8100), /* 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(8168, 0, 1424), /* filterTypeWithMethod $typeWithMethod */ + reg::packed(8183, 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(3728, MAY_BE_STRING), /* getPropertyReflection $propertyName */ - reg::packed(738, MAY_BE_NULL, 7684), /* getPropertyReflection return */ - reg::packed(8126, 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(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(8168, 0, 1424), /* getMethodReflection $typeWithMethod */ + reg::packed(8183, MAY_BE_STRING), /* getMethodReflection $methodName */ + reg::packed(738, MAY_BE_NULL, 8215), /* getMethodReflection return */ + reg::packed(8168, 0, 1424), /* getNakedMethod $typeWithMethod */ + reg::packed(8183, MAY_BE_STRING), /* getNakedMethod $methodName */ + reg::packed(738, MAY_BE_NULL, 8215), /* getNakedMethod return */ + reg::packed(8294, 0, 1424), /* getPropertyReflection $typeWithProperty */ + reg::packed(3859, MAY_BE_STRING), /* getPropertyReflection $propertyName */ + reg::packed(738, MAY_BE_NULL, 7852), /* getPropertyReflection return */ + reg::packed(8294, 0, 1424), /* getInstancePropertyReflection $typeWithProperty */ + reg::packed(3859, MAY_BE_STRING), /* getInstancePropertyReflection $propertyName */ + reg::packed(738, MAY_BE_NULL, 7852), /* getInstancePropertyReflection return */ + reg::packed(8294, 0, 1424), /* getStaticPropertyReflection $typeWithProperty */ + reg::packed(3859, MAY_BE_STRING), /* getStaticPropertyReflection $propertyName */ + reg::packed(738, MAY_BE_NULL, 7852), /* getStaticPropertyReflection return */ + reg::packed(8391, 0, 1424), /* getConstantReflection $typeWithConstant */ + reg::packed(8408, MAY_BE_STRING), /* getConstantReflection $constantName */ + reg::packed(738, MAY_BE_NULL, 8017), /* getConstantReflection return */ + reg::packed(8408, MAY_BE_STRING), /* getConstantExplicitTypeFromConfig $constantName */ + reg::packed(8443, 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(8529, 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 */ - reg::packed(5138, 0, 1424), /* getIterableKeyType $iteratee */ + reg::packed(5306, 0, 1424), /* getIterableKeyType $iteratee */ reg::packed(738, 0, 1424), /* getIterableKeyType return */ - reg::packed(5138, 0, 1424), /* getIterableValueType $iteratee */ + reg::packed(5306, 0, 1424), /* getIterableValueType $iteratee */ reg::packed(738, 0, 1424), /* getIterableValueType return */ - reg::packed(738, 0, 8463), /* getPhpVersion return */ + reg::packed(738, 0, 8631), /* getPhpVersion return */ reg::packed(738, MAY_BE_NULL, 1424), /* getConfiguredPhpVersionRange return */ - reg::packed(3874, 0, 1424), /* isOverallPhpVersionRange $type */ + reg::packed(4005, 0, 1424), /* isOverallPhpVersionRange $type */ reg::packed(738, MAY_BE_BOOL), /* isOverallPhpVersionRange return */ - reg::packed(1707, 0, 8555), /* invokeNodeCallback $node */ + reg::packed(1707, 0, 8723), /* 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(8757, MAY_BE_STRING), /* emitCollectedData $collectorType */ + reg::packed(8771, MAY_BE_ANY), /* emitCollectedData $data */ reg::packed(738, MAY_BE_VOID), /* emitCollectedData return */ - reg::packed(8626, MAY_BE_STRING), /* trackValueDependency $extensionClass */ + reg::packed(8794, 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(8830, 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(8855, MAY_BE_STRING), /* trackDirectoryDependency $directory */ + reg::packed(8865, MAY_BE_STRING, reg::NoString, false, false, 8873), /* trackDirectoryDependency $pattern */ reg::packed(738, MAY_BE_VOID), /* trackDirectoryDependency return */ - reg::packed(2977, MAY_BE_STRING), /* trackClassDependency $className */ + reg::packed(3108, MAY_BE_STRING), /* trackClassDependency $className */ reg::packed(738, MAY_BE_VOID), /* trackClassDependency return */ }; using Sig = reg::Sig; @@ -1099,135 +1112,140 @@ inline constexpr sigtab::Sig doNotTreatPhpDocTypesAsCertain = { { 2689 /* doNotT inline constexpr sigtab::Sig promoteNativeTypes = { { 2720 /* promoteNativeTypes */, 0, 133, 0, 133, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig resolveName = { { 2739 /* resolveName */, 1, 134, 1, 135, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig resolveTypeByName = { { 2782 /* resolveTypeByName */, 1, 136, 1, 137, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getTypeFromValue = { { 2806 /* getTypeFromValue */, 1, 138, 1, 139, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasExpressionType = { { 2823 /* hasExpressionType */, 1, 140, 1, 141, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getTrackedExpressionType = { { 2841 /* getTrackedExpressionType */, 1, 142, 1, 143, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig pushInFunctionCall = { { 2940 /* pushInFunctionCall */, 3, 144, 3, 147, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig popInFunctionCall = { { 2959 /* popInFunctionCall */, 0, 148, 0, 148, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isInClassExists = { { 2987 /* isInClassExists */, 1, 149, 1, 150, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFunctionCallStack = { { 3003 /* getFunctionCallStack */, 0, 151, 0, 151, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFunctionCallStackWithParameters = { { 3024 /* getFunctionCallStackWithParameters */, 0, 152, 0, 152, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isInFunctionExists = { { 3072 /* isInFunctionExists */, 1, 153, 1, 154, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterClass = { { 3107 /* enterClass */, 1, 155, 1, 156, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterTrait = { { 3134 /* enterTrait */, 1, 157, 1, 158, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterClassMethod = { { 3678 /* enterClassMethod */, 9, 159, 21, 180, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterPropertyHook = { { 3856 /* enterPropertyHook */, 10, 181, 11, 192, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig transformStaticType = { { 3879 /* transformStaticType */, 1, 193, 1, 194, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getRealParameterTypes = { { 3940 /* getRealParameterTypes */, 1, 195, 1, 196, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getRealParameterDefaultValues = { { 3962 /* getRealParameterDefaultValues */, 1, 197, 1, 198, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getParameterAttributes = { { 4082 /* getParameterAttributes */, 1, 199, 1, 200, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig enterFunction = { { 4208 /* enterFunction */, 8, 201, 17, 218, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterFunctionLike = { { 4266 /* enterFunctionLike */, 2, 219, 2, 221, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig enterNamespace = { { 4298 /* enterNamespace */, 1, 222, 1, 223, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterClosureBind = { { 4350 /* enterClosureBind */, 3, 224, 3, 227, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig restoreOriginalScopeAfterClosureBind = { { 4381 /* restoreOriginalScopeAfterClosureBind */, 1, 228, 1, 229, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig restoreThis = { { 4435 /* restoreThis */, 1, 230, 1, 231, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterClosureCall = { { 4447 /* enterClosureCall */, 2, 232, 2, 234, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isInClosureBind = { { 4464 /* isInClosureBind */, 0, 235, 0, 235, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig withClosureBindScopeClasses = { { 4480 /* withClosureBindScopeClasses */, 1, 236, 1, 237, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterAnonymousFunction = { { 4588 /* enterAnonymousFunction */, 2, 238, 3, 241, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterAnonymousFunctionWithoutReflection = { { 4611 /* enterAnonymousFunctionWithoutReflection */, 3, 242, 3, 245, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig expressionTypeIsUnchangeable = { { 4700 /* expressionTypeIsUnchangeable */, 1, 246, 1, 247, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig invalidateStaticExpressions = { { 4729 /* invalidateStaticExpressions */, 1, 248, 1, 249, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig enterArrowFunction = { { 4805 /* enterArrowFunction */, 2, 250, 3, 253, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterArrowFunctionWithoutReflection = { { 4824 /* enterArrowFunctionWithoutReflection */, 3, 254, 3, 257, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isParameterValueNullable = { { 4881 /* isParameterValueNullable */, 1, 258, 1, 259, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFunctionType = { { 4928 /* getFunctionType */, 3, 260, 3, 263, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getCallableParameterType = { { 4950 /* getCallableParameterType */, 3, 264, 3, 267, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig buildVariadicArrayTypeFromCallableParameters = { { 4986 /* buildVariadicArrayTypeFromCallableParameters */, 2, 268, 2, 270, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig intersectButNotNever = { { 5055 /* intersectButNotNever */, 2, 271, 2, 273, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig enterMatch = { { 5127 /* enterMatch */, 3, 274, 3, 277, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterForeach = { { 5208 /* enterForeach */, 7, 278, 7, 285, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterForeachKey = { { 5221 /* enterForeachKey */, 5, 286, 5, 291, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterCatchType = { { 5247 /* enterCatchType */, 2, 292, 2, 294, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterExpressionAssign = { { 5275 /* enterExpressionAssign */, 1, 295, 2, 297, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exitExpressionAssign = { { 5297 /* exitExpressionAssign */, 1, 298, 1, 299, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isInExpressionAssign = { { 5318 /* isInExpressionAssign */, 1, 300, 1, 301, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isInWriteExpressionAssign = { { 5339 /* isInWriteExpressionAssign */, 1, 302, 1, 303, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig setAllowedUndefinedExpression = { { 5365 /* setAllowedUndefinedExpression */, 1, 304, 1, 305, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig unsetAllowedUndefinedExpression = { { 5395 /* unsetAllowedUndefinedExpression */, 1, 306, 1, 307, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isUndefinedExpressionAllowed = { { 5427 /* isUndefinedExpressionAllowed */, 1, 308, 1, 309, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig assignVariable = { { 5492 /* assignVariable */, 4, 310, 5, 315, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig overwriteExpression = { { 5507 /* overwriteExpression */, 3, 316, 3, 319, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig resolveIntertwinedAssignedType = { { 5579 /* resolveIntertwinedAssignedType */, 5, 320, 5, 325, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isDimFetchPathReachable = { { 5653 /* isDimFetchPathReachable */, 2, 326, 2, 328, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig unsetExpression = { { 5677 /* unsetExpression */, 1, 329, 1, 330, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getStateType = { { 5693 /* getStateType */, 1, 331, 1, 332, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getScopeStateType = { { 5706 /* getScopeStateType */, 1, 333, 1, 334, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getScopeStateNativeType = { { 5724 /* getScopeStateNativeType */, 1, 335, 1, 336, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig resolveScopeStateType = { { 5748 /* resolveScopeStateType */, 2, 337, 2, 339, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig specifyExpressionType = { { 5770 /* specifyExpressionType */, 4, 340, 4, 344, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig openSpecificationScope = { { 5792 /* openSpecificationScope */, 0, 345, 0, 345, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isSpecifyExpressionTypeNoop = { { 5815 /* isSpecifyExpressionTypeNoop */, 2, 346, 2, 348, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig specifyExpressionTypeInPlace = { { 5843 /* specifyExpressionTypeInPlace */, 4, 349, 4, 353, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig assignExpression = { { 5872 /* assignExpression */, 3, 354, 3, 357, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig assignInitializedProperty = { { 5903 /* assignInitializedProperty */, 2, 358, 2, 360, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig invalidateExpression = { { 6012 /* invalidateExpression */, 1, 361, 4, 365, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isPrivatePropertyOfDifferentClass = { { 6033 /* isPrivatePropertyOfDifferentClass */, 2, 366, 2, 368, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig invalidateMethodsOnExpression = { { 6067 /* invalidateMethodsOnExpression */, 1, 369, 1, 370, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig setExpressionCertaintyKeepingType = { { 6097 /* setExpressionCertaintyKeepingType */, 2, 371, 2, 373, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isComplexUnionType = { { 6131 /* isComplexUnionType */, 1, 374, 1, 375, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig addTypeToExpression = { { 6150 /* addTypeToExpression */, 2, 376, 2, 378, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig removeTypeFromExpression = { { 6183 /* removeTypeFromExpression */, 2, 379, 2, 381, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig filterByTruthyValue = { { 6208 /* filterByTruthyValue */, 1, 382, 1, 383, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig filterByFalseyValue = { { 6228 /* filterByFalseyValue */, 1, 384, 1, 385, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig applySpecifiedTypes = { { 6263 /* applySpecifiedTypes */, 1, 386, 1, 387, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processConditionalExpressionsAfterSpecifying = { { 6304 /* processConditionalExpressionsAfterSpecifying */, 1, 388, 1, 389, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getConditionalExpressions = { { 6349 /* getConditionalExpressions */, 0, 390, 0, 390, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig addConditionalExpressions = { { 6404 /* addConditionalExpressions */, 2, 391, 2, 393, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exitFirstLevelStatements = { { 6430 /* exitFirstLevelStatements */, 0, 394, 0, 394, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isInFirstLevelStatement = { { 6455 /* isInFirstLevelStatement */, 0, 395, 0, 395, ZEND_ACC_PUBLIC } }; -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 getClosureBindScopeClassName = { { 2800 /* getClosureBindScopeClassName */, 0, 138, 0, 138, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isClosureBindScopeClassAmbiguous = { { 2829 /* isClosureBindScopeClassAmbiguous */, 0, 139, 0, 139, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getClosureBindScopeCommonAncestor = { { 2862 /* getClosureBindScopeCommonAncestor */, 1, 140, 1, 141, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getClosureBindScopeClassReflection = { { 2896 /* getClosureBindScopeClassReflection */, 0, 142, 0, 142, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getTypeFromValue = { { 2937 /* getTypeFromValue */, 1, 143, 1, 144, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasExpressionType = { { 2954 /* hasExpressionType */, 1, 145, 1, 146, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getTrackedExpressionType = { { 2972 /* getTrackedExpressionType */, 1, 147, 1, 148, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig pushInFunctionCall = { { 3071 /* pushInFunctionCall */, 3, 149, 3, 152, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig popInFunctionCall = { { 3090 /* popInFunctionCall */, 0, 153, 0, 153, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isInClassExists = { { 3118 /* isInClassExists */, 1, 154, 1, 155, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFunctionCallStack = { { 3134 /* getFunctionCallStack */, 0, 156, 0, 156, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFunctionCallStackWithParameters = { { 3155 /* getFunctionCallStackWithParameters */, 0, 157, 0, 157, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isInFunctionExists = { { 3203 /* isInFunctionExists */, 1, 158, 1, 159, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterClass = { { 3238 /* enterClass */, 1, 160, 1, 161, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterTrait = { { 3265 /* enterTrait */, 1, 162, 1, 163, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterClassMethod = { { 3809 /* enterClassMethod */, 9, 164, 21, 185, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterPropertyHook = { { 3987 /* enterPropertyHook */, 10, 186, 11, 197, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig transformStaticType = { { 4010 /* transformStaticType */, 1, 198, 1, 199, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getRealParameterTypes = { { 4071 /* getRealParameterTypes */, 1, 200, 1, 201, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getRealParameterDefaultValues = { { 4093 /* getRealParameterDefaultValues */, 1, 202, 1, 203, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getParameterAttributes = { { 4213 /* getParameterAttributes */, 1, 204, 1, 205, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig enterFunction = { { 4339 /* enterFunction */, 8, 206, 17, 223, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterFunctionLike = { { 4397 /* enterFunctionLike */, 2, 224, 2, 226, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig enterNamespace = { { 4429 /* enterNamespace */, 1, 227, 1, 228, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterClosureBind = { { 4481 /* enterClosureBind */, 3, 229, 3, 232, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig restoreOriginalScopeAfterClosureBind = { { 4512 /* restoreOriginalScopeAfterClosureBind */, 1, 233, 1, 234, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig restoreThis = { { 4566 /* restoreThis */, 1, 235, 1, 236, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterClosureCall = { { 4578 /* enterClosureCall */, 2, 237, 2, 239, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig closureBindScopeClassesOf = { { 4606 /* closureBindScopeClassesOf */, 1, 240, 1, 241, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isInClosureBind = { { 4632 /* isInClosureBind */, 0, 242, 0, 242, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig withClosureBindScopeClasses = { { 4648 /* withClosureBindScopeClasses */, 1, 243, 1, 244, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterAnonymousFunction = { { 4756 /* enterAnonymousFunction */, 2, 245, 3, 248, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterAnonymousFunctionWithoutReflection = { { 4779 /* enterAnonymousFunctionWithoutReflection */, 3, 249, 3, 252, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig expressionTypeIsUnchangeable = { { 4868 /* expressionTypeIsUnchangeable */, 1, 253, 1, 254, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig invalidateStaticExpressions = { { 4897 /* invalidateStaticExpressions */, 1, 255, 1, 256, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig enterArrowFunction = { { 4973 /* enterArrowFunction */, 2, 257, 3, 260, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterArrowFunctionWithoutReflection = { { 4992 /* enterArrowFunctionWithoutReflection */, 3, 261, 3, 264, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isParameterValueNullable = { { 5049 /* isParameterValueNullable */, 1, 265, 1, 266, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFunctionType = { { 5096 /* getFunctionType */, 3, 267, 3, 270, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getCallableParameterType = { { 5118 /* getCallableParameterType */, 3, 271, 3, 274, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig buildVariadicArrayTypeFromCallableParameters = { { 5154 /* buildVariadicArrayTypeFromCallableParameters */, 2, 275, 2, 277, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig intersectButNotNever = { { 5223 /* intersectButNotNever */, 2, 278, 2, 280, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig enterMatch = { { 5295 /* enterMatch */, 3, 281, 3, 284, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterForeach = { { 5376 /* enterForeach */, 7, 285, 7, 292, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterForeachKey = { { 5389 /* enterForeachKey */, 5, 293, 5, 298, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterCatchType = { { 5415 /* enterCatchType */, 2, 299, 2, 301, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterExpressionAssign = { { 5443 /* enterExpressionAssign */, 1, 302, 2, 304, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig exitExpressionAssign = { { 5465 /* exitExpressionAssign */, 1, 305, 1, 306, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isInExpressionAssign = { { 5486 /* isInExpressionAssign */, 1, 307, 1, 308, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isInWriteExpressionAssign = { { 5507 /* isInWriteExpressionAssign */, 1, 309, 1, 310, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig setAllowedUndefinedExpression = { { 5533 /* setAllowedUndefinedExpression */, 1, 311, 1, 312, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig unsetAllowedUndefinedExpression = { { 5563 /* unsetAllowedUndefinedExpression */, 1, 313, 1, 314, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isUndefinedExpressionAllowed = { { 5595 /* isUndefinedExpressionAllowed */, 1, 315, 1, 316, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig assignVariable = { { 5660 /* assignVariable */, 4, 317, 5, 322, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig overwriteExpression = { { 5675 /* overwriteExpression */, 3, 323, 3, 326, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig resolveIntertwinedAssignedType = { { 5747 /* resolveIntertwinedAssignedType */, 5, 327, 5, 332, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isDimFetchPathReachable = { { 5821 /* isDimFetchPathReachable */, 2, 333, 2, 335, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig unsetExpression = { { 5845 /* unsetExpression */, 1, 336, 1, 337, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getStateType = { { 5861 /* getStateType */, 1, 338, 1, 339, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getScopeStateType = { { 5874 /* getScopeStateType */, 1, 340, 1, 341, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getScopeStateNativeType = { { 5892 /* getScopeStateNativeType */, 1, 342, 1, 343, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig resolveScopeStateType = { { 5916 /* resolveScopeStateType */, 2, 344, 2, 346, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig specifyExpressionType = { { 5938 /* specifyExpressionType */, 4, 347, 4, 351, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig openSpecificationScope = { { 5960 /* openSpecificationScope */, 0, 352, 0, 352, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isSpecifyExpressionTypeNoop = { { 5983 /* isSpecifyExpressionTypeNoop */, 2, 353, 2, 355, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig specifyExpressionTypeInPlace = { { 6011 /* specifyExpressionTypeInPlace */, 4, 356, 4, 360, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig assignExpression = { { 6040 /* assignExpression */, 3, 361, 3, 364, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig assignInitializedProperty = { { 6071 /* assignInitializedProperty */, 2, 365, 2, 367, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig invalidateExpression = { { 6180 /* invalidateExpression */, 1, 368, 4, 372, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isPrivatePropertyOfDifferentClass = { { 6201 /* isPrivatePropertyOfDifferentClass */, 2, 373, 2, 375, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig invalidateMethodsOnExpression = { { 6235 /* invalidateMethodsOnExpression */, 1, 376, 1, 377, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig setExpressionCertaintyKeepingType = { { 6265 /* setExpressionCertaintyKeepingType */, 2, 378, 2, 380, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isComplexUnionType = { { 6299 /* isComplexUnionType */, 1, 381, 1, 382, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig addTypeToExpression = { { 6318 /* addTypeToExpression */, 2, 383, 2, 385, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig removeTypeFromExpression = { { 6351 /* removeTypeFromExpression */, 2, 386, 2, 388, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig filterByTruthyValue = { { 6376 /* filterByTruthyValue */, 1, 389, 1, 390, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig filterByFalseyValue = { { 6396 /* filterByFalseyValue */, 1, 391, 1, 392, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig applySpecifiedTypes = { { 6431 /* applySpecifiedTypes */, 1, 393, 1, 394, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processConditionalExpressionsAfterSpecifying = { { 6472 /* processConditionalExpressionsAfterSpecifying */, 1, 395, 1, 396, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getConditionalExpressions = { { 6517 /* getConditionalExpressions */, 0, 397, 0, 397, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig addConditionalExpressions = { { 6572 /* addConditionalExpressions */, 2, 398, 2, 400, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig exitFirstLevelStatements = { { 6598 /* exitFirstLevelStatements */, 0, 401, 0, 401, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isInFirstLevelStatement = { { 6623 /* isInFirstLevelStatement */, 0, 402, 0, 402, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig mergeWith = { { 6686 /* mergeWith */, 1, 403, 2, 405, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig collectAbsorbedClosures = { { 6708 /* collectAbsorbedClosures */, 2, 406, 2, 408, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mergeWithVariableState = { { 6732 /* mergeWithVariableState */, 1, 409, 2, 411, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig withoutPreciseClassConstantFetches = { { 6819 /* withoutPreciseClassConstantFetches */, 3, 412, 3, 415, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig preserveVacuousConditionalExpressions = { { 6934 /* preserveVacuousConditionalExpressions */, 3, 416, 3, 419, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mergeSameGuardConditionalExpressions = { { 7026 /* mergeSameGuardConditionalExpressions */, 3, 420, 3, 423, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mergeConditionalExpressions = { { 7120 /* mergeConditionalExpressions */, 2, 424, 2, 426, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mergeInitializedProperties = { { 7166 /* mergeInitializedProperties */, 1, 427, 1, 428, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processFinallyScope = { { 7227 /* processFinallyScope */, 2, 429, 2, 431, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processFinallyScopeVariableTypeHolders = { { 7325 /* processFinallyScopeVariableTypeHolders */, 3, 432, 3, 435, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processClosureScope = { { 7397 /* processClosureScope */, 3, 436, 3, 439, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processAlwaysIterableForeachScopeWithoutPollute = { { 7428 /* processAlwaysIterableForeachScopeWithoutPollute */, 1, 440, 1, 441, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig generalizeWith = { { 7498 /* generalizeWith */, 1, 442, 2, 444, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig generalizeWithVariableState = { { 7513 /* generalizeWithVariableState */, 2, 445, 2, 447, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig generalizeVariableTypeHolders = { { 7586 /* generalizeVariableTypeHolders */, 3, 448, 3, 451, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig flattenUnionForGeneralization = { { 7616 /* flattenUnionForGeneralization */, 1, 452, 1, 453, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig generalizeType = { { 7656 /* generalizeType */, 3, 454, 3, 457, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getArrayDepth = { { 7671 /* getArrayDepth */, 1, 458, 1, 459, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig equals = { { 7685 /* equals */, 1, 460, 1, 461, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig compareConditionalExpressions = { { 7720 /* compareConditionalExpressions */, 2, 462, 2, 464, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig compareVariableTypeHolders = { { 7750 /* compareVariableTypeHolders */, 2, 465, 2, 467, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig canAccessProperty = { { 7834 /* canAccessProperty */, 1, 468, 1, 469, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig canReadProperty = { { 7898 /* canReadProperty */, 1, 470, 1, 471, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig canWriteProperty = { { 7914 /* canWriteProperty */, 1, 472, 1, 473, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig canCallMethod = { { 7984 /* canCallMethod */, 1, 474, 1, 475, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig canAccessConstant = { { 8060 /* canAccessConstant */, 1, 476, 1, 477, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig canAccessClassMember = { { 8141 /* canAccessClassMember */, 1, 478, 1, 479, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig debug = { { 8162 /* debug */, 0, 480, 0, 480, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig filterTypeWithMethod = { { 8194 /* filterTypeWithMethod */, 2, 481, 2, 483, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getMethodReflection = { { 8259 /* getMethodReflection */, 2, 484, 2, 486, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getNakedMethod = { { 8279 /* getNakedMethod */, 2, 487, 2, 489, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getPropertyReflection = { { 8311 /* getPropertyReflection */, 2, 490, 2, 492, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getInstancePropertyReflection = { { 8333 /* getInstancePropertyReflection */, 2, 493, 2, 495, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getStaticPropertyReflection = { { 8363 /* getStaticPropertyReflection */, 2, 496, 2, 498, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantReflection = { { 8421 /* getConstantReflection */, 2, 499, 2, 501, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantExplicitTypeFromConfig = { { 8456 /* getConstantExplicitTypeFromConfig */, 2, 502, 2, 504, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantTypes = { { 8490 /* getConstantTypes */, 0, 505, 0, 505, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getGlobalConstantType = { { 8507 /* getGlobalConstantType */, 1, 506, 1, 507, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig createGlobalConstantFetches = { { 8540 /* createGlobalConstantFetches */, 3, 508, 3, 511, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNativeConstantTypes = { { 8568 /* getNativeConstantTypes */, 0, 512, 0, 512, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getIterableKeyType = { { 8591 /* getIterableKeyType */, 1, 513, 1, 514, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getIterableValueType = { { 8610 /* getIterableValueType */, 1, 515, 1, 516, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getPhpVersion = { { 8655 /* getPhpVersion */, 0, 517, 0, 517, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConfiguredPhpVersionRange = { { 8669 /* getConfiguredPhpVersionRange */, 0, 518, 0, 518, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOverallPhpVersionRange = { { 8698 /* isOverallPhpVersionRange */, 1, 519, 1, 520, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig invokeNodeCallback = { { 8738 /* invokeNodeCallback */, 1, 521, 1, 522, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig emitCollectedData = { { 8776 /* emitCollectedData */, 2, 523, 2, 525, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig trackValueDependency = { { 8809 /* trackValueDependency */, 2, 526, 2, 528, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig trackFileDependency = { { 8835 /* trackFileDependency */, 1, 529, 1, 530, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig trackDirectoryDependency = { { 8877 /* trackDirectoryDependency */, 1, 531, 2, 533, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig trackClassDependency = { { 8902 /* trackClassDependency */, 1, 534, 1, 535, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::MutatingScope diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index 6c0e26f9c3a..e7398501f21 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -2192,6 +2192,14 @@ zv::Val pt_mutating_scope_get_state_type(zend_object *scope, zend_object *expr); zv::Val pt_mutating_scope_get_conditional_expressions(zend_object *scope); zv::Val pt_mutating_scope_get_current_expression_result_storage(zend_object *scope); zv::Val pt_mutating_scope_resolve_type_by_name(zend_object *scope, zend_object *name); +zv::Val pt_mutating_scope_get_closure_bind_scope_class_reflection(zend_object *scope); +[[nodiscard]] bool pt_mutating_scope_is_closure_bind_scope_class_ambiguous(zend_object *scope, bool &out); +zv::Val pt_mutating_scope_get_closure_bind_scope_common_ancestor(zend_object *scope, zend_object *name); +/* MutatingScope::UNKNOWN_CLOSURE_BIND_SCOPE_CLASS — the class of a + * Closure::bind()/call() scope whose class is not known; the literal and its + * interned string (MutatingScope.cpp) are the only C++ copies */ +#define PT_UNKNOWN_CLOSURE_BIND_SCOPE_CLASS "*" +extern zend_string *pt_unknown_closure_bind_scope_class; zv::Val pt_mutating_scope_specify_types_of_new_world_handler_node(zend_object *scope, zend_object *node, zval *context); /* ReflectionAccess.cpp — $collection->getAll(): the memoized list out of a diff --git a/turbo-ext/tests/parser-visitors.php b/turbo-ext/tests/parser-visitors.php index e89ef34580d..ffbddfee3e5 100644 --- a/turbo-ext/tests/parser-visitors.php +++ b/turbo-ext/tests/parser-visitors.php @@ -73,6 +73,21 @@ 'bindTo($obj); $c->bindTo(); $c->bindTo(...); $c->BINDTO($obj); $c->other($obj);', ' SELF::$p, $obj); \Closure::bind(static function () { return parent::X; }, null, self::class); + Closure::bind(function () { return Closure::bind(fn () => static::B, null, Inner::class)() + self::C; }, null, Outer::class); + Closure::bind($callable, null, Foo::class); Closure::bind(...); static::D;', + // ... with the arguments found by name as well as by position + ' self::A, newScope: Foo::class, newThis: null); + Closure::bind(newScope: Bar::class, closure: function () { return static::B; }); + Closure::bind(function () { return parent::C; }, newScope: Baz::class); Closure::bind(fn () => self::D, $obj, newScope: Qux::class); + Closure::bind(closure: fn () => self::E); Closure::bind(Closure: fn () => self::F, newscope: X::class, other: 1);', + // ... duplicated, unpacked and nested arguments + ' 1, $o, Foo::class, newScope: Bar::class); Closure::bind($c, $o, closure: $d, newThis: $p); + Closure::bind(...$a, $o); Closure::bind(...$a); Closure::bind($c, ...$rest); Closure::bind(newThis: $o, closure: $c); + Closure::bind(fn () => 1, $o, Closure::bind(fn () => self::class, $p, X::class)()); + Closure::bind(Closure::bind(fn () => 1, $o, X::class), $p, Y::class);', ]; // plus real source files, so the ports meet whatever the repository contains diff --git a/turbo-ext/tests/scope-family-fixture.php b/turbo-ext/tests/scope-family-fixture.php index a01b8d79e05..307525bfc68 100644 --- a/turbo-ext/tests/scope-family-fixture.php +++ b/turbo-ext/tests/scope-family-fixture.php @@ -6,6 +6,33 @@ const ANSWER = 42; +// closures bound by Closure::bind() to a newScope that is not exactly one +// known class (top level: the walk keeps it), so the walk scopes inside them +// are bound to an unknown class, to several classes, or to none +/** @var class-string $plainScope */ +$plainScope = Holder::class; +/** @var class-string|class-string $siblingScope */ +$siblingScope = Child::class; +/** @var class-string|class-string $unrelatedScope */ +$unrelatedScope = Holder::class; +/** @var 'static'|null $noScope */ +$noScope = null; +\Closure::bind(static function (): void { + $inPlain = 1; +}, null, $plainScope); +\Closure::bind(static function (): void { + $inSibling = 1; +}, null, $siblingScope); +\Closure::bind(static function (): void { + $inUnrelated = 1; +}, null, $unrelatedScope); +\Closure::bind(static function (): void { + $inNoScope = 1; +}, null, $noScope); +\Closure::bind(static function (): void { + $inStatic = 1; +}, null, 'static'); + trait HelperTrait { @@ -188,3 +215,20 @@ public function siblingMethod(): int } } + +/** + * Closure::bind() calls for ParametersAcceptorSelector::selectFromArgs(): + * the bound closure's @param-closure-this type narrows the $newThis parameter + * wherever the closure argument is written + * + * @param-closure-this \stdClass $c + */ +function bindNamed(\Closure $c): void +{ + \Closure::bind($c, new Holder('a')); + \Closure::bind(closure: $c, newThis: new Holder('b')); + \Closure::bind(newThis: new Holder('c'), closure: $c); + \Closure::bind(newScope: Holder::class, newThis: new Holder('d'), closure: $c); + \Closure::bind(newThis: $c, closure: function (): void { + }); +} diff --git a/turbo-ext/tests/scope-family.php b/turbo-ext/tests/scope-family.php index b60fc4cf039..b9b58c052bf 100644 --- a/turbo-ext/tests/scope-family.php +++ b/turbo-ext/tests/scope-family.php @@ -1353,6 +1353,7 @@ public function exprRef(Node\Expr $expr): mixed 'getAnonymousFunctionReflection', 'getAnonymousFunctionReturnType', 'isInFirstLevelStatement', 'getDefinedVariables', 'getMaybeDefinedVariables', 'getExprPrinter', 'getCurrentTemplateArgumentFrame', 'getTemplateArgumentConstraints', 'getCurrentExpressionResultStorage', 'getFunctionCallStack', 'getFunctionCallStackWithParameters', 'isInClosureBind', + 'isClosureBindScopeClassAmbiguous', 'getClosureBindScopeClassReflection', ] as $method) { $observe($method, static fn () => $scope->$method()); } @@ -1467,6 +1468,7 @@ public function exprRef(Node\Expr $expr): mixed foreach ($sfProbeNames as $i => $name) { $observe("resolveName#$i " . $name->toString(), static fn () => $scope->resolveName($name)); $observe("resolveTypeByName#$i " . $name->toString(), static fn () => $scope->resolveTypeByName($name)); + $observe("getClosureBindScopeCommonAncestor#$i " . $name->toString(), static fn () => $scope->getClosureBindScopeCommonAncestor($name)); } foreach ($sfProbeValues as $i => $value) { $observe("getTypeFromValue#$i", static fn () => $scope->getTypeFromValue($value)); @@ -1644,6 +1646,9 @@ public function exprRef(Node\Expr $expr): mixed 'enterClosureBind(null)' => static fn () => $scope->enterClosureBind(null, null, []), 'enterClosureBind(static)' => static fn () => $scope->enterClosureBind(new \PHPStan\Type\ObjectType(\ScopeFamilyFixture\Holder::class), new \PHPStan\Type\ObjectWithoutClassType(), ['static']), 'enterClosureBind(Holder)' => static fn () => $scope->enterClosureBind(new \PHPStan\Type\ObjectType(\ScopeFamilyFixture\Holder::class), null, ['ScopeFamilyFixture\\Holder', 'Other']), + 'enterClosureBind(unknown)' => static fn () => $scope->enterClosureBind(null, null, [\PHPStan\Analyser\MutatingScope::UNKNOWN_CLOSURE_BIND_SCOPE_CLASS]), + 'enterClosureBind(siblings)' => static fn () => $scope->enterClosureBind(null, null, ['ScopeFamilyFixture\\Child', 'ScopeFamilyFixture\\Sibling']), + 'enterClosureCall(object)' => static fn () => $scope->enterClosureCall(new \PHPStan\Type\ObjectWithoutClassType(), new \PHPStan\Type\ObjectWithoutClassType()), 'restoreOriginalScopeAfterClosureBind(other)' => static fn () => $scope->restoreOriginalScopeAfterClosureBind($other), 'restoreOriginalScopeAfterClosureBind(this)' => static fn () => $scope->restoreOriginalScopeAfterClosureBind($scope), 'restoreThis(other)' => static fn () => $scope->restoreThis($other), @@ -2238,6 +2243,62 @@ public function exprRef(Node\Expr $expr): mixed } } +// ---- ParametersAcceptorSelector::selectFromArgs() over the fixture's +// Closure::bind() calls: the @param-closure-this override of $newThis, with +// the closure argument found by position or by the visitor's attribute. +// The walk above parses the fixture as a non-analysed file (function bodies +// cleaned away), so these calls come from a walk of the rich parser's AST, +// which also carries the visitors' attributes. Both sides get the walk scope +// and the walk's own (PHP) variants, so only the selector differs +/** @var list $sfClosureBindCalls */ +$sfClosureBindCalls = []; +$sfClosureBindCallback = static function (\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope) use (&$sfClosureBindCalls): void { + if ( + !$node instanceof \PhpParser\Node\Expr\StaticCall + || !$node->class instanceof \PhpParser\Node\Name + || $node->class->toLowerString() !== 'closure' + || !$node->name instanceof \PhpParser\Node\Identifier + || $node->name->toLowerString() !== 'bind' + ) { + return; + } + $walkScope = $scope->toWalkScope(); + $sfClosureBindCalls[] = [$node, $walkScope, $walkScope->getCurrentExpressionResultStorage()]; +}; +$sfResolver->processNodes( + $scContainer->getService('cachedRichParser')->parseFile($sfFile), + $sfScopeFactory->create(\PHPStan\Analyser\ScopeContext::create($sfFile), $sfClosureBindCallback), + $sfClosureBindCallback, +); +$sfClosureBindVariants = $sfReflectionProvider->getClass(\Closure::class)->getNativeMethod('bind')->getVariants(); +$sfClosureBindNarrowed = 0; +foreach ($sfClosureBindCalls as $sfCallIndex => [$sfCall, $sfCallScope, $sfCallStorage]) { + $sfCallStack = $sfHarness->constructorArgs($sfCallScope)['services']->expressionResultStorageStack; + $sfCallStack->push($sfCallStorage ?? new \PHPStan\Analyser\ExpressionResultStorage()); + try { + $sfSelected = []; + foreach (['php' => \PHPStan\Reflection\ParametersAcceptorSelector::class, 'native' => \PHPStanTurbo\ParametersAcceptorSelector::class] as $side => $sfSelector) { + try { + $sfAcceptor = $sfSelector::selectFromArgs($sfCallScope, $sfCall->getArgs(), $sfClosureBindVariants); + $sfSelected[$side] = array_map( + static fn ($parameter): string => $parameter->getName() . ': ' . $parameter->getType()->describe(\PHPStan\Type\VerbosityLevel::precise()), + $sfAcceptor->getParameters(), + ); + } catch (\Throwable $e) { + $sfSelected[$side] = get_class($e) . ': ' . $e->getMessage(); + } + } + } finally { + $sfCallStack->pop(); + } + if (is_array($sfSelected['php']) && in_array('newThis: stdClass', $sfSelected['php'], true)) { + $sfClosureBindNarrowed++; + } + check($sfSelected['php'] === $sfSelected['native'], sprintf('ParametersAcceptorSelector parity: selectFromArgs() over the Closure::bind() call on line %d: %s vs %s', $sfCall->getStartLine(), json_encode($sfSelected['php']), json_encode($sfSelected['native']))); +} +check(count($sfClosureBindCalls) >= 5, 'scope-family: the fixture walk saw the Closure::bind() calls (' . count($sfClosureBindCalls) . ')'); +check($sfClosureBindNarrowed === 4, "scope-family: the closure-this override narrowed \$newThis in 4 Closure::bind() calls ($sfClosureBindNarrowed)"); + if (getenv('SF_DUMP_LABEL') !== false) { $sfDumpLabel = getenv('SF_DUMP_LABEL'); foreach ($sfObservations['php'] as $sfId => $sfPhpObservations) { diff --git a/turbo-ext/tests/walk-trace-fixtures/closure-bind-named-arguments.php b/turbo-ext/tests/walk-trace-fixtures/closure-bind-named-arguments.php new file mode 100644 index 00000000000..f0d36ca4590 --- /dev/null +++ b/turbo-ext/tests/walk-trace-fixtures/closure-bind-named-arguments.php @@ -0,0 +1,33 @@ +x; + }); + $b = \Closure::bind(newThis: $foo, newScope: Foo::class, closure: function () { + return [$this->x, Foo::$s]; + }); + $c = \Closure::bind(newScope: Foo::class, closure: function () { + return $this; + }, newThis: $foo); + $d = \Closure::bind(function () { + return $this->x; + }, newScope: Foo::class, newThis: $foo); + $e = \Closure::bind(closure: fn () => $this->x, newScope: Foo::class, newThis: $foo); +} diff --git a/turbo-ext/tests/walk-trace-fixtures/closure-bind-scope-ambiguous.php b/turbo-ext/tests/walk-trace-fixtures/closure-bind-scope-ambiguous.php new file mode 100644 index 00000000000..fbf4eb129ab --- /dev/null +++ b/turbo-ext/tests/walk-trace-fixtures/closure-bind-scope-ambiguous.php @@ -0,0 +1,59 @@ +|class-string $withAncestor + * @param class-string|class-string $noAncestor + * @param class-string $plain + */ +function f(string $withAncestor, string $noAncestor, string $plain, string $str, ?string $nullable): void +{ + \Closure::bind(static fn () => [static::make(), self::make(), self::base(), self::A, new self(), new static()], null, $withAncestor)(); + \Closure::bind(static fn () => [self::A, new self(), static::A], null, $noAncestor)(); + \Closure::bind(static fn () => [self::A, new self(), parent::A], null, $plain)(); + \Closure::bind(static fn () => [self::A, new self()], null, $str)(); + \Closure::bind(static fn () => [self::A, new self()], null, $nullable)(); + \Closure::bind(static fn () => [self::A, new self()], null, 'static')(); + \Closure::bind(static fn () => [self::A, new self()], null, null)(); +} + +class Container +{ + public const A = 'Container'; + + /** + * @param class-string|class-string $withAncestor + * @param class-string $plain + */ + public function run(string $withAncestor, string $plain): void + { + \Closure::bind(static fn () => [self::A, new self(), static::A], null, $withAncestor)(); + \Closure::bind(static fn () => [self::A, new self(), static::A], null, $plain)(); + } +} diff --git a/turbo-ext/tests/walk-trace-fixtures/closure-bind-scope-class.php b/turbo-ext/tests/walk-trace-fixtures/closure-bind-scope-class.php new file mode 100644 index 00000000000..0057f82d57d --- /dev/null +++ b/turbo-ext/tests/walk-trace-fixtures/closure-bind-scope-class.php @@ -0,0 +1,42 @@ + [new self(), new static(), self::sm(), self::$sp, self::A, static::A], null, Foo::class)(); +\Closure::bind(static fn () => [new self(), new static(), self::sm(), self::$sp, self::A], null, $foo)(); +\Closure::bind(static fn () => [new parent(), parent::sm(), parent::A], null, new Bar())(); +\Closure::bind(static fn () => [new parent(), parent::X], null, NoParent::class)(); +\Closure::bind(static fn () => [new self(), self::A], null, 'static')(); +\Closure::bind(fn () => [self::im(), $this->im()], new Foo(), Foo::class)(); + +class Container +{ + public function run(): void + { + \Closure::bind(static fn () => [new self(), new parent(), parent::sm(), self::A], null, Bar::class)(); + \Closure::bind(static fn () => [new self(), self::class], null, self::class)(); + } +} diff --git a/turbo-ext/tests/walk-trace-fixtures/closure-bind-scope-named-arguments.php b/turbo-ext/tests/walk-trace-fixtures/closure-bind-scope-named-arguments.php new file mode 100644 index 00000000000..ee094b531b1 --- /dev/null +++ b/turbo-ext/tests/walk-trace-fixtures/closure-bind-scope-named-arguments.php @@ -0,0 +1,28 @@ +x; + $b = self::$s; + return $a; + }); + $gn = \Closure::bind(newScope: Bar::class, closure: function () { + return $this; + }, newThis: new Foo()); +} diff --git a/turbo-ext/tests/walk-trace-fixtures/closure-bind-scope-nested-class.php b/turbo-ext/tests/walk-trace-fixtures/closure-bind-scope-nested-class.php new file mode 100644 index 00000000000..a760a5e1964 --- /dev/null +++ b/turbo-ext/tests/walk-trace-fixtures/closure-bind-scope-nested-class.php @@ -0,0 +1,23 @@ + new class extends Foo { + public const Z = 'z'; + public function f(): array { return [self::Z, static::Z, self::A, parent::A, new self(), new static(), new parent()]; } +}, null, Foo::class)(); + +\Closure::bind(static function (): void { + function walkTraceClosureBindInner(): void + { + echo self::A; + } + (static fn () => [self::A, new self()])(); +}, null, Foo::class)();