@@ -854,113 +854,62 @@ public function specifyTypesInCondition(
854854 $ this ->create ($ dimFetch , $ arrayType ->getIterableValueType (), TypeSpecifierContext::createTrue (), $ scope ),
855855 );
856856 } 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.
863857 $ keyType = $ scope ->getType ($ expr ->expr );
864858 $ 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 );
869-
870- $ holder = new ConditionalExpressionHolder (
871- [$ keyExprString => ExpressionTypeHolder::createYes ($ expr ->var , $ nonNullKeyType )],
872- ExpressionTypeHolder::createYes ($ dimFetch , $ arrayType ->getIterableValueType ()),
873- );
874-
875- $ specifiedTypes = $ specifiedTypes ->unionWith (
876- (new SpecifiedTypes ([], []))->setNewConditionalExpressionHolders ([
877- $ dimFetchString => [$ holder ->getKey () => $ holder ],
878- ]),
879- );
880- }
881- }
882- }
883- }
884-
885- // infer $arr[$key] after $key = array_search($needle, $arr)
886- if (
887- $ expr ->expr instanceof FuncCall
888- && $ expr ->expr ->name instanceof Name
889- && $ expr ->expr ->name ->toLowerString () === 'array_search '
890- && count ($ expr ->expr ->getArgs ()) >= 2
891- ) {
892- $ arrayArg = $ expr ->expr ->getArgs ()[1 ]->value ;
893- $ arrayType = $ scope ->getType ($ arrayArg );
894-
895- if ($ arrayType ->isArray ()->yes ()) {
896- if ($ context ->true ()) {
897- $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
898-
899- $ specifiedTypes = $ specifiedTypes ->unionWith (
900- $ this ->create ($ dimFetch , $ arrayType ->getIterableValueType (), TypeSpecifierContext::createTrue (), $ scope ),
901- );
902- } elseif ($ expr ->var instanceof Expr \Variable && is_string ($ expr ->var ->name )) {
903- $ keyType = $ scope ->getType ($ expr ->expr );
904- $ nonFalseKeyType = TypeCombinator::remove ($ keyType , new ConstantBooleanType (false ));
905- if (!$ nonFalseKeyType instanceof NeverType && !$ keyType ->isFalse ()->yes ()) {
906- $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
907- $ dimFetchString = $ this ->exprPrinter ->printExpr ($ dimFetch );
908- $ keyExprString = $ this ->exprPrinter ->printExpr ($ expr ->var );
909-
910- $ holder = new ConditionalExpressionHolder (
911- [$ keyExprString => ExpressionTypeHolder::createYes ($ expr ->var , $ nonFalseKeyType )],
912- ExpressionTypeHolder::createYes ($ dimFetch , $ arrayType ->getIterableValueType ()),
913- );
914-
859+ if (!$ nonNullKeyType instanceof NeverType) {
915860 $ specifiedTypes = $ specifiedTypes ->unionWith (
916- (new SpecifiedTypes ([], []))->setNewConditionalExpressionHolders ([
917- $ dimFetchString => [$ holder ->getKey () => $ holder ],
918- ]),
861+ $ this ->createArrayDimFetchConditionalExpressionHolder ($ expr ->var , $ arrayArg , $ arrayType , $ nonNullKeyType ),
919862 );
920863 }
921864 }
922865 }
923866 }
924867
925- // infer $arr[$key] after $key = array_find_key($arr, $callback)
868+ // infer $arr[$key] after $key = array_search($needle, $arr) or $key = array_find_key($arr, $callback)
926869 if (
927870 $ expr ->expr instanceof FuncCall
928871 && $ expr ->expr ->name instanceof Name
929- && $ expr ->expr ->name ->toLowerString () === 'array_find_key '
930872 && count ($ expr ->expr ->getArgs ()) >= 2
931873 ) {
932- $ arrayArg = $ expr ->expr ->getArgs ()[0 ]->value ;
933- $ arrayType = $ scope ->getType ($ arrayArg );
934-
935- if ($ arrayType ->isArray ()->yes ()) {
936- if ($ context ->true ()) {
937- $ specifiedTypes = $ specifiedTypes ->unionWith (
938- $ this ->create ($ arrayArg , new NonEmptyArrayType (), TypeSpecifierContext::createTrue (), $ scope ),
939- );
874+ $ funcName = $ expr ->expr ->name ->toLowerString ();
875+ $ arrayArgIndex = null ;
876+ $ sentinelType = null ;
877+ $ narrowToNonEmpty = false ;
878+
879+ if ($ funcName === 'array_search ' ) {
880+ $ arrayArgIndex = 1 ;
881+ $ sentinelType = new ConstantBooleanType (false );
882+ } elseif ($ funcName === 'array_find_key ' ) {
883+ $ arrayArgIndex = 0 ;
884+ $ sentinelType = new NullType ();
885+ $ narrowToNonEmpty = true ;
886+ }
887+
888+ if ($ arrayArgIndex !== null && $ sentinelType !== null ) {
889+ $ arrayArg = $ expr ->expr ->getArgs ()[$ arrayArgIndex ]->value ;
890+ $ arrayType = $ scope ->getType ($ arrayArg );
940891
941- $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
892+ if ($ arrayType ->isArray ()->yes ()) {
893+ if ($ context ->true ()) {
894+ if ($ narrowToNonEmpty ) {
895+ $ specifiedTypes = $ specifiedTypes ->unionWith (
896+ $ this ->create ($ arrayArg , new NonEmptyArrayType (), TypeSpecifierContext::createTrue (), $ scope ),
897+ );
898+ }
942899
943- $ specifiedTypes = $ specifiedTypes ->unionWith (
944- $ this ->create ($ dimFetch , $ arrayType ->getIterableValueType (), TypeSpecifierContext::createTrue (), $ scope ),
945- );
946- } elseif ($ expr ->var instanceof Expr \Variable && is_string ($ expr ->var ->name )) {
947- $ keyType = $ scope ->getType ($ expr ->expr );
948- $ nonNullKeyType = TypeCombinator::removeNull ($ keyType );
949- if (!$ nonNullKeyType instanceof NeverType && !$ keyType ->isNull ()->yes ()) {
950900 $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
951- $ dimFetchString = $ this ->exprPrinter ->printExpr ($ dimFetch );
952- $ keyExprString = $ this ->exprPrinter ->printExpr ($ expr ->var );
953-
954- $ holder = new ConditionalExpressionHolder (
955- [$ keyExprString => ExpressionTypeHolder::createYes ($ expr ->var , $ nonNullKeyType )],
956- ExpressionTypeHolder::createYes ($ dimFetch , $ arrayType ->getIterableValueType ()),
957- );
958901
959902 $ specifiedTypes = $ specifiedTypes ->unionWith (
960- (new SpecifiedTypes ([], []))->setNewConditionalExpressionHolders ([
961- $ dimFetchString => [$ holder ->getKey () => $ holder ],
962- ]),
903+ $ this ->create ($ dimFetch , $ arrayType ->getIterableValueType (), TypeSpecifierContext::createTrue (), $ scope ),
963904 );
905+ } elseif ($ expr ->var instanceof Expr \Variable && is_string ($ expr ->var ->name )) {
906+ $ keyType = $ scope ->getType ($ expr ->expr );
907+ $ narrowedKeyType = TypeCombinator::remove ($ keyType , $ sentinelType );
908+ if (!$ narrowedKeyType instanceof NeverType) {
909+ $ specifiedTypes = $ specifiedTypes ->unionWith (
910+ $ this ->createArrayDimFetchConditionalExpressionHolder ($ expr ->var , $ arrayArg , $ arrayType , $ narrowedKeyType ),
911+ );
912+ }
964913 }
965914 }
966915 }
@@ -2466,6 +2415,27 @@ public function create(
24662415 return $ types ;
24672416 }
24682417
2418+ private function createArrayDimFetchConditionalExpressionHolder (
2419+ Expr \Variable $ keyVar ,
2420+ Expr $ arrayArg ,
2421+ Type $ arrayType ,
2422+ Type $ narrowedKeyType ,
2423+ ): SpecifiedTypes
2424+ {
2425+ $ dimFetch = new ArrayDimFetch ($ arrayArg , $ keyVar );
2426+ $ dimFetchString = $ this ->exprPrinter ->printExpr ($ dimFetch );
2427+ $ keyExprString = $ this ->exprPrinter ->printExpr ($ keyVar );
2428+
2429+ $ holder = new ConditionalExpressionHolder (
2430+ [$ keyExprString => ExpressionTypeHolder::createYes ($ keyVar , $ narrowedKeyType )],
2431+ ExpressionTypeHolder::createYes ($ dimFetch , $ arrayType ->getIterableValueType ()),
2432+ );
2433+
2434+ return (new SpecifiedTypes ([], []))->setNewConditionalExpressionHolders ([
2435+ $ dimFetchString => [$ holder ->getKey () => $ holder ],
2436+ ]);
2437+ }
2438+
24692439 private function createForExpr (
24702440 Expr $ expr ,
24712441 Type $ type ,
0 commit comments