@@ -848,113 +848,62 @@ public function specifyTypesInCondition(
848848 $ this ->create ($ dimFetch , $ arrayType ->getIterableValueType (), TypeSpecifierContext::createTrue (), $ scope ),
849849 );
850850 } elseif ($ expr ->var instanceof Expr \Variable && is_string ($ expr ->var ->name )) {
851- // The array might be empty here, so we cannot register
852- // $arr[$key] unconditionally. Attach a conditional holder
853- // that fires once the user narrows $key to non-null
854- // (e.g. `if ($key !== null)`), giving the deep-write
855- // path the same shape information `isset($arr[$key])`
856- // would have provided.
857851 $ keyType = $ scope ->getType ($ expr ->expr );
858852 $ nonNullKeyType = TypeCombinator::removeNull ($ keyType );
859- if (!$ nonNullKeyType instanceof NeverType && !$ keyType ->isNull ()->yes ()) {
860- $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
861- $ dimFetchString = $ this ->exprPrinter ->printExpr ($ dimFetch );
862- $ keyExprString = $ this ->exprPrinter ->printExpr ($ expr ->var );
863-
864- $ holder = new ConditionalExpressionHolder (
865- [$ keyExprString => ExpressionTypeHolder::createYes ($ expr ->var , $ nonNullKeyType )],
866- ExpressionTypeHolder::createYes ($ dimFetch , $ arrayType ->getIterableValueType ()),
867- );
868-
869- $ specifiedTypes = $ specifiedTypes ->unionWith (
870- (new SpecifiedTypes ([], []))->setNewConditionalExpressionHolders ([
871- $ dimFetchString => [$ holder ->getKey () => $ holder ],
872- ]),
873- );
874- }
875- }
876- }
877- }
878-
879- // infer $arr[$key] after $key = array_search($needle, $arr)
880- if (
881- $ expr ->expr instanceof FuncCall
882- && $ expr ->expr ->name instanceof Name
883- && $ expr ->expr ->name ->toLowerString () === 'array_search '
884- && count ($ expr ->expr ->getArgs ()) >= 2
885- ) {
886- $ arrayArg = $ expr ->expr ->getArgs ()[1 ]->value ;
887- $ arrayType = $ scope ->getType ($ arrayArg );
888-
889- if ($ arrayType ->isArray ()->yes ()) {
890- if ($ context ->true ()) {
891- $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
892-
893- $ specifiedTypes = $ specifiedTypes ->unionWith (
894- $ this ->create ($ dimFetch , $ arrayType ->getIterableValueType (), TypeSpecifierContext::createTrue (), $ scope ),
895- );
896- } elseif ($ expr ->var instanceof Expr \Variable && is_string ($ expr ->var ->name )) {
897- $ keyType = $ scope ->getType ($ expr ->expr );
898- $ nonFalseKeyType = TypeCombinator::remove ($ keyType , new ConstantBooleanType (false ));
899- if (!$ nonFalseKeyType instanceof NeverType && !$ keyType ->isFalse ()->yes ()) {
900- $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
901- $ dimFetchString = $ this ->exprPrinter ->printExpr ($ dimFetch );
902- $ keyExprString = $ this ->exprPrinter ->printExpr ($ expr ->var );
903-
904- $ holder = new ConditionalExpressionHolder (
905- [$ keyExprString => ExpressionTypeHolder::createYes ($ expr ->var , $ nonFalseKeyType )],
906- ExpressionTypeHolder::createYes ($ dimFetch , $ arrayType ->getIterableValueType ()),
907- );
908-
853+ if (!$ nonNullKeyType instanceof NeverType) {
909854 $ specifiedTypes = $ specifiedTypes ->unionWith (
910- (new SpecifiedTypes ([], []))->setNewConditionalExpressionHolders ([
911- $ dimFetchString => [$ holder ->getKey () => $ holder ],
912- ]),
855+ $ this ->createArrayDimFetchConditionalExpressionHolder ($ expr ->var , $ arrayArg , $ arrayType , $ nonNullKeyType ),
913856 );
914857 }
915858 }
916859 }
917860 }
918861
919- // infer $arr[$key] after $key = array_find_key($arr, $callback)
862+ // infer $arr[$key] after $key = array_search($needle, $arr) or $key = array_find_key($arr, $callback)
920863 if (
921864 $ expr ->expr instanceof FuncCall
922865 && $ expr ->expr ->name instanceof Name
923- && $ expr ->expr ->name ->toLowerString () === 'array_find_key '
924866 && count ($ expr ->expr ->getArgs ()) >= 2
925867 ) {
926- $ arrayArg = $ expr ->expr ->getArgs ()[0 ]->value ;
927- $ arrayType = $ scope ->getType ($ arrayArg );
928-
929- if ($ arrayType ->isArray ()->yes ()) {
930- if ($ context ->true ()) {
931- $ specifiedTypes = $ specifiedTypes ->unionWith (
932- $ this ->create ($ arrayArg , new NonEmptyArrayType (), TypeSpecifierContext::createTrue (), $ scope ),
933- );
868+ $ funcName = $ expr ->expr ->name ->toLowerString ();
869+ $ arrayArgIndex = null ;
870+ $ sentinelType = null ;
871+ $ narrowToNonEmpty = false ;
872+
873+ if ($ funcName === 'array_search ' ) {
874+ $ arrayArgIndex = 1 ;
875+ $ sentinelType = new ConstantBooleanType (false );
876+ } elseif ($ funcName === 'array_find_key ' ) {
877+ $ arrayArgIndex = 0 ;
878+ $ sentinelType = new NullType ();
879+ $ narrowToNonEmpty = true ;
880+ }
881+
882+ if ($ arrayArgIndex !== null && $ sentinelType !== null ) {
883+ $ arrayArg = $ expr ->expr ->getArgs ()[$ arrayArgIndex ]->value ;
884+ $ arrayType = $ scope ->getType ($ arrayArg );
934885
935- $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
886+ if ($ arrayType ->isArray ()->yes ()) {
887+ if ($ context ->true ()) {
888+ if ($ narrowToNonEmpty ) {
889+ $ specifiedTypes = $ specifiedTypes ->unionWith (
890+ $ this ->create ($ arrayArg , new NonEmptyArrayType (), TypeSpecifierContext::createTrue (), $ scope ),
891+ );
892+ }
936893
937- $ specifiedTypes = $ specifiedTypes ->unionWith (
938- $ this ->create ($ dimFetch , $ arrayType ->getIterableValueType (), TypeSpecifierContext::createTrue (), $ scope ),
939- );
940- } elseif ($ expr ->var instanceof Expr \Variable && is_string ($ expr ->var ->name )) {
941- $ keyType = $ scope ->getType ($ expr ->expr );
942- $ nonNullKeyType = TypeCombinator::removeNull ($ keyType );
943- if (!$ nonNullKeyType instanceof NeverType && !$ keyType ->isNull ()->yes ()) {
944894 $ dimFetch = new ArrayDimFetch ($ arrayArg , $ expr ->var );
945- $ dimFetchString = $ this ->exprPrinter ->printExpr ($ dimFetch );
946- $ keyExprString = $ this ->exprPrinter ->printExpr ($ expr ->var );
947-
948- $ holder = new ConditionalExpressionHolder (
949- [$ keyExprString => ExpressionTypeHolder::createYes ($ expr ->var , $ nonNullKeyType )],
950- ExpressionTypeHolder::createYes ($ dimFetch , $ arrayType ->getIterableValueType ()),
951- );
952895
953896 $ specifiedTypes = $ specifiedTypes ->unionWith (
954- (new SpecifiedTypes ([], []))->setNewConditionalExpressionHolders ([
955- $ dimFetchString => [$ holder ->getKey () => $ holder ],
956- ]),
897+ $ this ->create ($ dimFetch , $ arrayType ->getIterableValueType (), TypeSpecifierContext::createTrue (), $ scope ),
957898 );
899+ } elseif ($ expr ->var instanceof Expr \Variable && is_string ($ expr ->var ->name )) {
900+ $ keyType = $ scope ->getType ($ expr ->expr );
901+ $ narrowedKeyType = TypeCombinator::remove ($ keyType , $ sentinelType );
902+ if (!$ narrowedKeyType instanceof NeverType) {
903+ $ specifiedTypes = $ specifiedTypes ->unionWith (
904+ $ this ->createArrayDimFetchConditionalExpressionHolder ($ expr ->var , $ arrayArg , $ arrayType , $ narrowedKeyType ),
905+ );
906+ }
958907 }
959908 }
960909 }
@@ -2455,6 +2404,27 @@ public function create(
24552404 return $ types ;
24562405 }
24572406
2407+ private function createArrayDimFetchConditionalExpressionHolder (
2408+ Expr \Variable $ keyVar ,
2409+ Expr $ arrayArg ,
2410+ Type $ arrayType ,
2411+ Type $ narrowedKeyType ,
2412+ ): SpecifiedTypes
2413+ {
2414+ $ dimFetch = new ArrayDimFetch ($ arrayArg , $ keyVar );
2415+ $ dimFetchString = $ this ->exprPrinter ->printExpr ($ dimFetch );
2416+ $ keyExprString = $ this ->exprPrinter ->printExpr ($ keyVar );
2417+
2418+ $ holder = new ConditionalExpressionHolder (
2419+ [$ keyExprString => ExpressionTypeHolder::createYes ($ keyVar , $ narrowedKeyType )],
2420+ ExpressionTypeHolder::createYes ($ dimFetch , $ arrayType ->getIterableValueType ()),
2421+ );
2422+
2423+ return (new SpecifiedTypes ([], []))->setNewConditionalExpressionHolders ([
2424+ $ dimFetchString => [$ holder ->getKey () => $ holder ],
2425+ ]);
2426+ }
2427+
24582428 private function createForExpr (
24592429 Expr $ expr ,
24602430 Type $ type ,
0 commit comments