1111use PhpParser \Node \Expr \FuncCall ;
1212use PHPStan \Analyser \Scope ;
1313use PHPStan \Reflection \FunctionReflection ;
14+ use PHPStan \Type \Accessory \NonEmptyArrayType ;
15+ use PHPStan \Type \ArrayType ;
1416use PHPStan \Type \Constant \ConstantArrayType ;
17+ use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
1518use PHPStan \Type \StringType ;
1619use PHPStan \Type \Type ;
1720use PHPStan \Type \TypeCombinator ;
@@ -31,20 +34,49 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
3134 public function getTypeFromFunctionCall (FunctionReflection $ functionReflection , FuncCall $ functionCall , Scope $ scope ): ?Type
3235 {
3336 $ args = $ functionCall ->getArgs ();
34- if ($ args === []) {
37+
38+ if (count ($ args ) < 2 ) {
3539 return null ;
3640 }
3741
38- $ type = $ scope ->getType ($ args [0 ]->value );
42+ $ pairsType = $ scope ->getType ($ args [0 ]->value );
43+ $ attsType = $ scope ->getType ($ args [1 ]->value );
44+
45+ if ($ attsType ->isIterableAtLeastOnce ()->no ()) {
46+ return $ pairsType ;
47+ }
48+
49+ if (! $ this ->hasConstantArrays ($ pairsType )) {
50+ return $ this ->resolveTypeForNonConstantPairs ($ pairsType );
51+ }
3952
40- if (count ( $ type -> getConstantArrays ()) === 0 ) {
41- return $ type ;
53+ if (! $ this -> hasConstantArrays ( $ attsType ) ) {
54+ return $ this -> resolveTypeForNonConstantAtts ( $ pairsType ) ;
4255 }
4356
44- $ returnType = [];
45- foreach ($ type ->getConstantArrays () as $ constantArray ) {
46- // shortcode_atts values are coming from the defined defaults or from the actual string shortcode attributes
47- $ returnType [] = new ConstantArrayType (
57+ return $ this ->resolveTypeForConstantAtts ($ pairsType , $ attsType );
58+ }
59+
60+ protected function resolveTypeForNonConstantPairs (Type $ pairsType ): Type
61+ {
62+ $ keyType = $ pairsType ->getIterableKeyType ();
63+ $ valueType = TypeCombinator::union (
64+ $ pairsType ->getIterableValueType (),
65+ new StringType ()
66+ );
67+ $ arrayType = new ArrayType ($ keyType , $ valueType );
68+
69+ return $ pairsType ->isIterableAtLeastOnce ()->yes ()
70+ ? TypeCombinator::intersect ($ arrayType , new NonEmptyArrayType ())
71+ : $ arrayType ;
72+ }
73+
74+ protected function resolveTypeForNonConstantAtts (Type $ pairsType ): Type
75+ {
76+ $ types = [];
77+
78+ foreach ($ pairsType ->getConstantArrays () as $ constantArray ) {
79+ $ types [] = new ConstantArrayType (
4880 $ constantArray ->getKeyTypes (),
4981 array_map (
5082 static function (Type $ valueType ): Type {
@@ -55,6 +87,52 @@ static function (Type $valueType): Type {
5587 );
5688 }
5789
58- return TypeCombinator::union (...$ returnType );
90+ return TypeCombinator::union (...$ types );
91+ }
92+
93+ protected function resolveTypeForConstantAtts (Type $ pairsType , Type $ attsType ): Type
94+ {
95+ $ types = [];
96+
97+ foreach ($ pairsType ->getConstantArrays () as $ constantPairsArray ) {
98+ foreach ($ attsType ->getConstantArrays () as $ constantAttsArray ) {
99+ $ types [] = $ this ->mergeArrays ($ constantPairsArray , $ constantAttsArray );
100+ }
101+ }
102+
103+ return TypeCombinator::union (...$ types );
104+ }
105+
106+ protected function mergeArrays (ConstantArrayType $ pairsArray , ConstantArrayType $ attsArray ): Type
107+ {
108+ if (count ($ attsArray ->getKeyTypes ()) === 0 ) {
109+ return $ pairsArray ;
110+ }
111+
112+ $ builder = ConstantArrayTypeBuilder::createFromConstantArray ($ pairsArray );
113+
114+ foreach ($ pairsArray ->getKeyTypes () as $ keyType ) {
115+ $ hasOffsetValueType = $ attsArray ->hasOffsetValueType ($ keyType );
116+
117+ if ($ hasOffsetValueType ->no ()) {
118+ continue ;
119+ }
120+
121+ $ valueType = $ hasOffsetValueType ->yes ()
122+ ? $ attsArray ->getOffsetValueType ($ keyType )
123+ : TypeCombinator::union (
124+ $ pairsArray ->getOffsetValueType ($ keyType ),
125+ $ attsArray ->getOffsetValueType ($ keyType )
126+ );
127+
128+ $ builder ->setOffsetValueType ($ keyType , $ valueType );
129+ }
130+
131+ return $ builder ->getArray ();
132+ }
133+
134+ protected function hasConstantArrays (Type $ type ): bool
135+ {
136+ return count ($ type ->getConstantArrays ()) > 0 ;
59137 }
60138}
0 commit comments