@@ -849,34 +849,77 @@ public function specifyTypesInCondition(
849849
850850 if ($ isNonEmpty ) {
851851 $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
852-
853852 $ specifiedTypes = $ specifiedTypes ->unionWith (
854853 $ this ->create ($ dimFetch , $ arrayType ->getIterableValueType (), TypeSpecifierContext::createTrue (), $ scope ),
855854 );
856855 } elseif ($ expr ->var instanceof Expr \Variable && is_string ($ expr ->var ->name )) {
857- // The array might be empty here, so we cannot register
858- // $arr[$key] unconditionally. Attach a conditional holder
859- // that fires once the user narrows $key to non-null
860- // (e.g. `if ($key !== null)`), giving the deep-write
861- // path the same shape information `isset($arr[$key])`
862- // would have provided.
863856 $ keyType = $ scope ->getType ($ expr ->expr );
864857 $ nonNullKeyType = TypeCombinator::removeNull ($ keyType );
865- if (!$ nonNullKeyType instanceof NeverType && !$ keyType ->isNull ()->yes ()) {
866- $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
867- $ dimFetchString = $ this ->exprPrinter ->printExpr ($ dimFetch );
868- $ keyExprString = $ this ->exprPrinter ->printExpr ($ expr ->var );
858+ if (!$ nonNullKeyType instanceof NeverType) {
859+ $ specifiedTypes = $ specifiedTypes ->unionWith (
860+ $ this ->createArrayDimFetchConditionalExpressionHolder ($ expr ->var , $ arrayArg , $ nonNullKeyType , $ arrayType ->getIterableValueType ()),
861+ );
862+ }
863+ }
864+ }
865+ }
866+
867+ // infer $arr[$key] after $key = array_search($needle, $arr) or $key = array_find_key($arr, $callback)
868+ if (
869+ $ expr ->expr instanceof FuncCall
870+ && $ expr ->expr ->name instanceof Name
871+ && !$ expr ->expr ->isFirstClassCallable ()
872+ && count ($ expr ->expr ->getArgs ()) >= 2
873+ ) {
874+ $ funcName = $ expr ->expr ->name ->toLowerString ();
875+ $ arrayArg = null ;
876+ $ sentinelType = null ;
877+ $ isStrictArraySearch = false ;
878+
879+ if ($ funcName === 'array_search ' ) {
880+ $ arrayArg = $ expr ->expr ->getArgs ()[1 ]->value ;
881+ $ sentinelType = new ConstantBooleanType (false );
882+ $ isStrictArraySearch = count ($ expr ->expr ->getArgs ()) >= 3 && $ scope ->getType ($ expr ->expr ->getArgs ()[2 ]->value )->isTrue ()->yes ();
883+ } elseif ($ funcName === 'array_find_key ' ) {
884+ $ arrayArg = $ expr ->expr ->getArgs ()[0 ]->value ;
885+ $ sentinelType = new NullType ();
886+ }
869887
870- $ holder = new ConditionalExpressionHolder (
871- [$ keyExprString => ExpressionTypeHolder::createYes ($ expr ->var , $ nonNullKeyType )],
872- ExpressionTypeHolder::createYes ($ dimFetch , $ arrayType ->getIterableValueType ()),
888+ if ($ arrayArg !== null ) {
889+ $ arrayType = $ scope ->getType ($ arrayArg );
890+
891+ if ($ arrayType ->isArray ()->yes ()) {
892+ if ($ context ->true ()) {
893+ $ specifiedTypes = $ specifiedTypes ->unionWith (
894+ $ this ->create ($ arrayArg , new NonEmptyArrayType (), TypeSpecifierContext::createTrue (), $ scope ),
873895 );
874896
897+ $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
898+
899+ if ($ isStrictArraySearch ) {
900+ $ needleType = $ scope ->getType ($ expr ->expr ->getArgs ()[0 ]->value );
901+ $ dimFetchType = TypeCombinator::intersect ($ needleType , $ arrayType ->getIterableValueType ());
902+ } else {
903+ $ dimFetchType = $ arrayType ->getIterableValueType ();
904+ }
905+
875906 $ specifiedTypes = $ specifiedTypes ->unionWith (
876- (new SpecifiedTypes ([], []))->setNewConditionalExpressionHolders ([
877- $ dimFetchString => [$ holder ->getKey () => $ holder ],
878- ]),
907+ $ this ->create ($ dimFetch , $ dimFetchType , TypeSpecifierContext::createTrue (), $ scope ),
879908 );
909+ } elseif ($ expr ->var instanceof Expr \Variable && is_string ($ expr ->var ->name )) {
910+ $ keyType = $ scope ->getType ($ expr ->expr );
911+ $ narrowedKeyType = TypeCombinator::remove ($ keyType , $ sentinelType );
912+ if (!$ narrowedKeyType instanceof NeverType) {
913+ if ($ isStrictArraySearch ) {
914+ $ needleType = $ scope ->getType ($ expr ->expr ->getArgs ()[0 ]->value );
915+ $ dimFetchType = TypeCombinator::intersect ($ needleType , $ arrayType ->getIterableValueType ());
916+ } else {
917+ $ dimFetchType = $ arrayType ->getIterableValueType ();
918+ }
919+ $ specifiedTypes = $ specifiedTypes ->unionWith (
920+ $ this ->createArrayDimFetchConditionalExpressionHolder ($ expr ->var , $ arrayArg , $ narrowedKeyType , $ dimFetchType ),
921+ );
922+ }
880923 }
881924 }
882925 }
@@ -941,28 +984,6 @@ public function specifyTypesInCondition(
941984 return $ specifiedTypes ;
942985 }
943986
944- if ($ context ->true ()) {
945- // infer $arr[$key] after $key = array_search($needle, $arr)
946- if (
947- $ expr ->expr instanceof FuncCall
948- && $ expr ->expr ->name instanceof Name
949- && !$ expr ->expr ->isFirstClassCallable ()
950- && $ expr ->expr ->name ->toLowerString () === 'array_search '
951- && count ($ expr ->expr ->getArgs ()) >= 2
952- ) {
953- $ arrayArg = $ expr ->expr ->getArgs ()[1 ]->value ;
954- $ arrayType = $ scope ->getType ($ arrayArg );
955-
956- if ($ arrayType ->isArray ()->yes ()) {
957- $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
958- $ iterableValueType = $ arrayType ->getIterableValueType ();
959-
960- return $ specifiedTypes ->unionWith (
961- $ this ->create ($ dimFetch , $ iterableValueType , TypeSpecifierContext::createTrue (), $ scope ),
962- );
963- }
964- }
965- }
966987 return $ specifiedTypes ;
967988 } elseif (
968989 $ expr instanceof Expr \Isset_
@@ -2404,6 +2425,27 @@ public function create(
24042425 return $ types ;
24052426 }
24062427
2428+ private function createArrayDimFetchConditionalExpressionHolder (
2429+ Expr \Variable $ keyVar ,
2430+ Expr $ arrayArg ,
2431+ Type $ narrowedKeyType ,
2432+ Type $ dimFetchType ,
2433+ ): SpecifiedTypes
2434+ {
2435+ $ dimFetch = new ArrayDimFetch ($ arrayArg , $ keyVar );
2436+ $ dimFetchString = $ this ->exprPrinter ->printExpr ($ dimFetch );
2437+ $ keyExprString = $ this ->exprPrinter ->printExpr ($ keyVar );
2438+
2439+ $ holder = new ConditionalExpressionHolder (
2440+ [$ keyExprString => ExpressionTypeHolder::createYes ($ keyVar , $ narrowedKeyType )],
2441+ ExpressionTypeHolder::createYes ($ dimFetch , $ dimFetchType ),
2442+ );
2443+
2444+ return (new SpecifiedTypes ([], []))->setNewConditionalExpressionHolders ([
2445+ $ dimFetchString => [$ holder ->getKey () => $ holder ],
2446+ ]);
2447+ }
2448+
24072449 private function createForExpr (
24082450 Expr $ expr ,
24092451 Type $ type ,
@@ -3032,11 +3074,12 @@ private function resolveNormalizedIdentical(Expr\BinaryOp\Identical $expr, Scope
30323074
30333075 // array_key_first($a) !== null
30343076 // array_key_last($a) !== null
3077+ // array_find_key($a, $cb) !== null
30353078 if (
30363079 $ unwrappedLeftExpr instanceof FuncCall
30373080 && $ unwrappedLeftExpr ->name instanceof Name
30383081 && !$ unwrappedLeftExpr ->isFirstClassCallable ()
3039- && in_array ($ unwrappedLeftExpr ->name ->toLowerString (), ['array_key_first ' , 'array_key_last ' ], true )
3082+ && in_array ($ unwrappedLeftExpr ->name ->toLowerString (), ['array_key_first ' , 'array_key_last ' , ' array_find_key ' ], true )
30403083 && isset ($ unwrappedLeftExpr ->getArgs ()[0 ])
30413084 && $ rightType ->isNull ()->yes ()
30423085 ) {
0 commit comments