@@ -2719,6 +2719,7 @@ public function processClosureNode(
27192719
27202720 $ closureScope = $ scope ->enterAnonymousFunction ($ expr , $ callableParameters );
27212721 $ closureScope = $ closureScope ->processClosureScope ($ scope , null , $ byRefUses );
2722+ $ closureScope = $ this ->applyArrayKeysSourceToScope ($ expr , $ closureScope );
27222723 $ closureType = $ closureScope ->getAnonymousFunctionReflection ();
27232724 if (!$ closureType instanceof ClosureType) {
27242725 throw new ShouldNotHappenException ();
@@ -2881,6 +2882,7 @@ public function processArrowFunctionNode(
28812882 $ arrowFunctionCallArgs ,
28822883 $ passedToType ,
28832884 ));
2885+ $ arrowFunctionScope = $ this ->applyArrayKeysSourceToScope ($ expr , $ arrowFunctionScope );
28842886 $ arrowFunctionType = $ arrowFunctionScope ->getAnonymousFunctionReflection ();
28852887 if ($ arrowFunctionType === null ) {
28862888 throw new ShouldNotHappenException ();
@@ -3331,6 +3333,8 @@ public function processArgs(
33313333 }
33323334 }
33333335
3336+ $ this ->detectArrayKeysInSiblingArgs ($ args , $ i , $ arg ->value );
3337+
33343338 $ this ->callNodeCallbackWithExpression ($ nodeCallback , $ arg ->value , $ scopeToPass , $ storage , $ context );
33353339 $ closureResult = $ this ->processClosureNode ($ stmt , $ arg ->value , $ scopeToPass , $ storage , $ nodeCallback , $ context , $ parameterType ?? null );
33363340 if ($ this ->callCallbackImmediately ($ parameter , $ parameterType , $ calleeReflection )) {
@@ -3389,6 +3393,8 @@ public function processArgs(
33893393 }
33903394 }
33913395
3396+ $ this ->detectArrayKeysInSiblingArgs ($ args , $ i , $ arg ->value );
3397+
33923398 $ this ->callNodeCallbackWithExpression ($ nodeCallback , $ arg ->value , $ scopeToPass , $ storage , $ context );
33933399 $ arrowFunctionResult = $ this ->processArrowFunctionNode ($ stmt , $ arg ->value , $ scopeToPass , $ storage , $ nodeCallback , $ parameterType ?? null );
33943400 if ($ this ->callCallbackImmediately ($ parameter , $ parameterType , $ calleeReflection )) {
@@ -3920,6 +3926,78 @@ private function enterForeach(MutatingScope $scope, ExpressionResultStorage $sto
39203926 return $ this ->processVarAnnotation ($ scope , $ vars , $ stmt );
39213927 }
39223928
3929+ private const ARRAY_KEYS_SOURCE_ATTRIBUTE = 'arrayKeysSourceExprs ' ;
3930+
3931+ /**
3932+ * @param Arg[] $args
3933+ */
3934+ private function detectArrayKeysInSiblingArgs (array $ args , int $ currentIndex , Expr $ callbackExpr ): void
3935+ {
3936+ $ arrayKeysSourceExprs = [];
3937+ foreach ($ args as $ j => $ otherArg ) {
3938+ if ($ j === $ currentIndex ) {
3939+ continue ;
3940+ }
3941+ if (
3942+ !($ otherArg ->value instanceof FuncCall)
3943+ || !($ otherArg ->value ->name instanceof Name)
3944+ || $ otherArg ->value ->name ->toLowerString () !== 'array_keys '
3945+ ) {
3946+ continue ;
3947+ }
3948+
3949+ $ funcArgs = $ otherArg ->value ->getArgs ();
3950+ if (count ($ funcArgs ) !== 1 ) {
3951+ continue ;
3952+ }
3953+
3954+ $ arrayKeysSourceExprs [] = $ funcArgs [0 ]->value ;
3955+ }
3956+ if ($ arrayKeysSourceExprs === []) {
3957+ return ;
3958+ }
3959+
3960+ $ callbackExpr ->setAttribute (self ::ARRAY_KEYS_SOURCE_ATTRIBUTE , $ arrayKeysSourceExprs );
3961+ }
3962+
3963+ private function applyArrayKeysSourceToScope (Expr $ callbackExpr , MutatingScope $ scope ): MutatingScope
3964+ {
3965+ /** @var Expr[]|null $arrayKeysSourceExprs */
3966+ $ arrayKeysSourceExprs = $ callbackExpr ->getAttribute (self ::ARRAY_KEYS_SOURCE_ATTRIBUTE );
3967+ if ($ arrayKeysSourceExprs === null ) {
3968+ return $ scope ;
3969+ }
3970+
3971+ $ params = [];
3972+ if ($ callbackExpr instanceof Expr \ArrowFunction || $ callbackExpr instanceof Expr \Closure) {
3973+ $ params = $ callbackExpr ->params ;
3974+ }
3975+
3976+ foreach ($ arrayKeysSourceExprs as $ sourceExpr ) {
3977+ $ sourceType = $ scope ->getType ($ sourceExpr );
3978+ $ sourceNativeType = $ scope ->getNativeType ($ sourceExpr );
3979+ $ keyType = $ sourceType ->getIterableKeyType ();
3980+
3981+ foreach ($ params as $ param ) {
3982+ if (!$ param ->var instanceof Variable || !is_string ($ param ->var ->name )) {
3983+ continue ;
3984+ }
3985+ $ paramType = $ scope ->getVariableType ($ param ->var ->name );
3986+ if (!$ keyType ->isSuperTypeOf ($ paramType )->yes ()) {
3987+ continue ;
3988+ }
3989+
3990+ $ scope = $ scope ->assignExpression (
3991+ new ArrayDimFetch ($ sourceExpr , $ param ->var ),
3992+ $ sourceType ->getIterableValueType (),
3993+ $ sourceNativeType ->getIterableValueType (),
3994+ );
3995+ }
3996+ }
3997+
3998+ return $ scope ;
3999+ }
4000+
39234001 /**
39244002 * @param callable(Node $node, Scope $scope): void $nodeCallback
39254003 */
0 commit comments