@@ -1978,99 +1978,11 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
1978
1978
&& in_array ($ functionReflection ->getName (), ['array_push ' , 'array_unshift ' ], true )
1979
1979
&& count ($ expr ->getArgs ()) >= 2
1980
1980
) {
1981
- $ arrayArg = $ expr ->getArgs ()[0 ]->value ;
1982
- $ arrayType = $ scope ->getType ($ arrayArg );
1983
- $ callArgs = array_slice ($ expr ->getArgs (), 1 );
1984
-
1985
- /**
1986
- * @param Arg[] $callArgs
1987
- * @param callable(?Type, Type, bool): void $setOffsetValueType
1988
- */
1989
- $ setOffsetValueTypes = static function (Scope $ scope , array $ callArgs , callable $ setOffsetValueType , ?bool &$ nonConstantArrayWasUnpacked = null ): void {
1990
- foreach ($ callArgs as $ callArg ) {
1991
- $ callArgType = $ scope ->getType ($ callArg ->value );
1992
- if ($ callArg ->unpack ) {
1993
- if (count ($ callArgType ->getConstantArrays ()) === 1 ) {
1994
- $ iterableValueTypes = $ callArgType ->getConstantArrays ()[0 ]->getValueTypes ();
1995
- } else {
1996
- $ iterableValueTypes = [$ callArgType ->getIterableValueType ()];
1997
- $ nonConstantArrayWasUnpacked = true ;
1998
- }
1999
-
2000
- $ isOptional = !$ callArgType ->isIterableAtLeastOnce ()->yes ();
2001
- foreach ($ iterableValueTypes as $ iterableValueType ) {
2002
- if ($ iterableValueType instanceof UnionType) {
2003
- foreach ($ iterableValueType ->getTypes () as $ innerType ) {
2004
- $ setOffsetValueType (null , $ innerType , $ isOptional );
2005
- }
2006
- } else {
2007
- $ setOffsetValueType (null , $ iterableValueType , $ isOptional );
2008
- }
2009
- }
2010
- continue ;
2011
- }
2012
- $ setOffsetValueType (null , $ callArgType , false );
2013
- }
2014
- };
2015
-
2016
- $ constantArrays = $ arrayType ->getConstantArrays ();
2017
- if (count ($ constantArrays ) > 0 ) {
2018
- $ newArrayTypes = [];
2019
- $ prepend = $ functionReflection ->getName () === 'array_unshift ' ;
2020
- foreach ($ constantArrays as $ constantArray ) {
2021
- $ arrayTypeBuilder = $ prepend ? ConstantArrayTypeBuilder::createEmpty () : ConstantArrayTypeBuilder::createFromConstantArray ($ constantArray );
2022
-
2023
- $ setOffsetValueTypes (
2024
- $ scope ,
2025
- $ callArgs ,
2026
- static function (?Type $ offsetType , Type $ valueType , bool $ optional ) use (&$ arrayTypeBuilder ): void {
2027
- $ arrayTypeBuilder ->setOffsetValueType ($ offsetType , $ valueType , $ optional );
2028
- },
2029
- $ nonConstantArrayWasUnpacked ,
2030
- );
2031
-
2032
- if ($ prepend ) {
2033
- $ keyTypes = $ constantArray ->getKeyTypes ();
2034
- $ valueTypes = $ constantArray ->getValueTypes ();
2035
- foreach ($ keyTypes as $ k => $ keyType ) {
2036
- $ arrayTypeBuilder ->setOffsetValueType (
2037
- count ($ keyType ->getConstantStrings ()) === 1 ? $ keyType ->getConstantStrings ()[0 ] : null ,
2038
- $ valueTypes [$ k ],
2039
- $ constantArray ->isOptionalKey ($ k ),
2040
- );
2041
- }
2042
- }
1981
+ $ arrayType = $ this ->getArrayFunctionAppendingType ($ functionReflection , $ scope , $ expr );
1982
+ $ arrayNativeType = $ this ->getArrayFunctionAppendingType ($ functionReflection , $ scope ->doNotTreatPhpDocTypesAsCertain (), $ expr );
2043
1983
2044
- $ constantArray = $ arrayTypeBuilder ->getArray ();
2045
-
2046
- if ($ constantArray ->isConstantArray ()->yes () && $ nonConstantArrayWasUnpacked ) {
2047
- $ array = new ArrayType ($ constantArray ->generalize (GeneralizePrecision::lessSpecific ())->getIterableKeyType (), $ constantArray ->getIterableValueType ());
2048
- $ constantArray = $ constantArray ->isIterableAtLeastOnce ()->yes ()
2049
- ? TypeCombinator::intersect ($ array , new NonEmptyArrayType ())
2050
- : $ array ;
2051
- }
2052
-
2053
- $ newArrayTypes [] = $ constantArray ;
2054
- }
2055
-
2056
- $ arrayType = TypeCombinator::union (...$ newArrayTypes );
2057
- } else {
2058
- $ setOffsetValueTypes (
2059
- $ scope ,
2060
- $ callArgs ,
2061
- static function (?Type $ offsetType , Type $ valueType , bool $ optional ) use (&$ arrayType ): void {
2062
- $ isIterableAtLeastOnce = $ arrayType ->isIterableAtLeastOnce ()->yes () || !$ optional ;
2063
- $ arrayType = $ arrayType ->setOffsetValueType ($ offsetType , $ valueType );
2064
- if ($ isIterableAtLeastOnce ) {
2065
- return ;
2066
- }
2067
-
2068
- $ arrayType = new ArrayType ($ arrayType ->getIterableKeyType (), $ arrayType ->getIterableValueType ());
2069
- },
2070
- );
2071
- }
2072
-
2073
- $ scope = $ scope ->invalidateExpression ($ arrayArg )->assignExpression ($ arrayArg , $ arrayType , $ scope ->getNativeType ($ arrayArg ));
1984
+ $ arrayArg = $ expr ->getArgs ()[0 ]->value ;
1985
+ $ scope = $ scope ->invalidateExpression ($ arrayArg )->assignExpression ($ arrayArg , $ arrayType , $ arrayNativeType );
2074
1986
}
2075
1987
2076
1988
if (
@@ -2927,6 +2839,103 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
2927
2839
);
2928
2840
}
2929
2841
2842
+ private function getArrayFunctionAppendingType (FunctionReflection $ functionReflection , Scope $ scope , FuncCall $ expr ): Type
2843
+ {
2844
+ $ arrayArg = $ expr ->getArgs ()[0 ]->value ;
2845
+ $ arrayType = $ scope ->getType ($ arrayArg );
2846
+ $ callArgs = array_slice ($ expr ->getArgs (), 1 );
2847
+
2848
+ /**
2849
+ * @param Arg[] $callArgs
2850
+ * @param callable(?Type, Type, bool): void $setOffsetValueType
2851
+ */
2852
+ $ setOffsetValueTypes = static function (Scope $ scope , array $ callArgs , callable $ setOffsetValueType , ?bool &$ nonConstantArrayWasUnpacked = null ): void {
2853
+ foreach ($ callArgs as $ callArg ) {
2854
+ $ callArgType = $ scope ->getType ($ callArg ->value );
2855
+ if ($ callArg ->unpack ) {
2856
+ if (count ($ callArgType ->getConstantArrays ()) === 1 ) {
2857
+ $ iterableValueTypes = $ callArgType ->getConstantArrays ()[0 ]->getValueTypes ();
2858
+ } else {
2859
+ $ iterableValueTypes = [$ callArgType ->getIterableValueType ()];
2860
+ $ nonConstantArrayWasUnpacked = true ;
2861
+ }
2862
+
2863
+ $ isOptional = !$ callArgType ->isIterableAtLeastOnce ()->yes ();
2864
+ foreach ($ iterableValueTypes as $ iterableValueType ) {
2865
+ if ($ iterableValueType instanceof UnionType) {
2866
+ foreach ($ iterableValueType ->getTypes () as $ innerType ) {
2867
+ $ setOffsetValueType (null , $ innerType , $ isOptional );
2868
+ }
2869
+ } else {
2870
+ $ setOffsetValueType (null , $ iterableValueType , $ isOptional );
2871
+ }
2872
+ }
2873
+ continue ;
2874
+ }
2875
+ $ setOffsetValueType (null , $ callArgType , false );
2876
+ }
2877
+ };
2878
+
2879
+ $ constantArrays = $ arrayType ->getConstantArrays ();
2880
+ if (count ($ constantArrays ) > 0 ) {
2881
+ $ newArrayTypes = [];
2882
+ $ prepend = $ functionReflection ->getName () === 'array_unshift ' ;
2883
+ foreach ($ constantArrays as $ constantArray ) {
2884
+ $ arrayTypeBuilder = $ prepend ? ConstantArrayTypeBuilder::createEmpty () : ConstantArrayTypeBuilder::createFromConstantArray ($ constantArray );
2885
+
2886
+ $ setOffsetValueTypes (
2887
+ $ scope ,
2888
+ $ callArgs ,
2889
+ static function (?Type $ offsetType , Type $ valueType , bool $ optional ) use (&$ arrayTypeBuilder ): void {
2890
+ $ arrayTypeBuilder ->setOffsetValueType ($ offsetType , $ valueType , $ optional );
2891
+ },
2892
+ $ nonConstantArrayWasUnpacked ,
2893
+ );
2894
+
2895
+ if ($ prepend ) {
2896
+ $ keyTypes = $ constantArray ->getKeyTypes ();
2897
+ $ valueTypes = $ constantArray ->getValueTypes ();
2898
+ foreach ($ keyTypes as $ k => $ keyType ) {
2899
+ $ arrayTypeBuilder ->setOffsetValueType (
2900
+ count ($ keyType ->getConstantStrings ()) === 1 ? $ keyType ->getConstantStrings ()[0 ] : null ,
2901
+ $ valueTypes [$ k ],
2902
+ $ constantArray ->isOptionalKey ($ k ),
2903
+ );
2904
+ }
2905
+ }
2906
+
2907
+ $ constantArray = $ arrayTypeBuilder ->getArray ();
2908
+
2909
+ if ($ constantArray ->isConstantArray ()->yes () && $ nonConstantArrayWasUnpacked ) {
2910
+ $ array = new ArrayType ($ constantArray ->generalize (GeneralizePrecision::lessSpecific ())->getIterableKeyType (), $ constantArray ->getIterableValueType ());
2911
+ $ constantArray = $ constantArray ->isIterableAtLeastOnce ()->yes ()
2912
+ ? TypeCombinator::intersect ($ array , new NonEmptyArrayType ())
2913
+ : $ array ;
2914
+ }
2915
+
2916
+ $ newArrayTypes [] = $ constantArray ;
2917
+ }
2918
+
2919
+ return TypeCombinator::union (...$ newArrayTypes );
2920
+ }
2921
+
2922
+ $ setOffsetValueTypes (
2923
+ $ scope ,
2924
+ $ callArgs ,
2925
+ static function (?Type $ offsetType , Type $ valueType , bool $ optional ) use (&$ arrayType ): void {
2926
+ $ isIterableAtLeastOnce = $ arrayType ->isIterableAtLeastOnce ()->yes () || !$ optional ;
2927
+ $ arrayType = $ arrayType ->setOffsetValueType ($ offsetType , $ valueType );
2928
+ if ($ isIterableAtLeastOnce ) {
2929
+ return ;
2930
+ }
2931
+
2932
+ $ arrayType = new ArrayType ($ arrayType ->getIterableKeyType (), $ arrayType ->getIterableValueType ());
2933
+ },
2934
+ );
2935
+
2936
+ return $ arrayType ;
2937
+ }
2938
+
2930
2939
private function getFunctionThrowPoint (
2931
2940
FunctionReflection $ functionReflection ,
2932
2941
?ParametersAcceptor $ parametersAcceptor ,
0 commit comments