2
2
3
3
namespace PHPStan \Type \Php ;
4
4
5
+ <<<<<<< HEAD
6
+ =======
7
+ use PhpParser \Node \Arg ;
8
+ use PhpParser \Node \Expr ;
9
+ use PhpParser \Node \Expr \BinaryOp \BitwiseOr ;
10
+ use PhpParser \Node \Expr \ConstFetch ;
11
+ >>>>>>> Extend JsonThrowOnErrorDynamicReturnTypeExtension to detect knonw type from contssant string value
5
12
use PhpParser \Node \Expr \FuncCall;
6
13
use PhpParser \Node \Name \FullyQualified ;
7
14
use PHPStan \Analyser \Scope ;
8
15
use PHPStan \Reflection \FunctionReflection ;
9
16
use PHPStan \Reflection \ParametersAcceptorSelector ;
10
17
use PHPStan \Reflection \ReflectionProvider ;
18
+ <<<<<<< HEAD
11
19
use PHPStan \Type \BitwiseFlagHelper;
12
20
use PHPStan \Type \Constant \ConstantBooleanType ;
21
+ =======
22
+ use PHPStan \Type \ArrayType ;
23
+ use PHPStan \Type \BooleanType ;
24
+ use PHPStan \Type \Constant \ConstantBooleanType ;
25
+ use PHPStan \Type \Constant \ConstantIntegerType ;
26
+ use PHPStan \Type \Constant \ConstantStringType ;
27
+ use PHPStan \Type \ConstantTypeHelper ;
28
+ >>>>>>> Extend JsonThrowOnErrorDynamicReturnTypeExtension to detect knonw type from contssant string value
13
29
use PHPStan \Type \DynamicFunctionReturnTypeExtension;
30
+ use PHPStan \Type \FloatType ;
31
+ use PHPStan \Type \IntegerType ;
32
+ use PHPStan \Type \MixedType ;
33
+ use PHPStan \Type \ObjectType ;
34
+ use PHPStan \Type \StringType ;
14
35
use PHPStan \Type \Type ;
15
36
use PHPStan \Type \TypeCombinator ;
16
- use function in_array ;
37
+ use PHPStan \Type \UnionType ;
38
+ use stdClass ;
39
+ use function json_decode ;
17
40
18
41
class JsonThrowOnErrorDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
19
42
{
@@ -35,14 +58,11 @@ public function isFunctionSupported(
35
58
FunctionReflection $ functionReflection ,
36
59
): bool
37
60
{
38
- return $ this ->reflectionProvider ->hasConstant (new FullyQualified ('JSON_THROW_ON_ERROR ' ), null ) && in_array (
39
- $ functionReflection ->getName (),
40
- [
41
- 'json_encode ' ,
42
- 'json_decode ' ,
43
- ],
44
- true ,
45
- );
61
+ if ($ functionReflection ->getName () === 'json_decode ' ) {
62
+ return true ;
63
+ }
64
+
65
+ return $ this ->reflectionProvider ->hasConstant (new FullyQualified ('JSON_THROW_ON_ERROR ' ), null ) && $ functionReflection ->getName () === 'json_encode ' ;
46
66
}
47
67
48
68
public function getTypeFromFunctionCall (
@@ -51,8 +71,19 @@ public function getTypeFromFunctionCall(
51
71
Scope $ scope ,
52
72
): Type
53
73
{
74
+ // update type based on JSON_THROW_ON_ERROR
54
75
$ argumentPosition = $ this ->argumentPositions [$ functionReflection ->getName ()];
55
76
$ defaultReturnType = ParametersAcceptorSelector::selectSingle ($ functionReflection ->getVariants ())->getReturnType ();
77
+
78
+ // narrow type for json_decode()
79
+ if ($ functionReflection ->getName () === 'json_decode ' ) {
80
+ $ jsonDecodeNarrowedType = $ this ->narrowTypeForJsonDecode ($ functionCall , $ scope );
81
+ // improve type
82
+ if (! $ jsonDecodeNarrowedType instanceof MixedType) {
83
+ $ defaultReturnType = $ jsonDecodeNarrowedType ;
84
+ }
85
+ }
86
+
56
87
if (!isset ($ functionCall ->getArgs ()[$ argumentPosition ])) {
57
88
return $ defaultReturnType ;
58
89
}
@@ -65,4 +96,73 @@ public function getTypeFromFunctionCall(
65
96
return $ defaultReturnType ;
66
97
}
67
98
99
+ private function narrowTypeForJsonDecode (FuncCall $ funcCall , Scope $ scope ): Type
100
+ {
101
+ $ args = $ funcCall ->getArgs ();
102
+ $ isForceArray = $ this ->isForceArray ($ funcCall );
103
+
104
+ $ firstArgValue = $ args [0 ]->value ;
105
+ $ firstValueType = $ scope ->getType ($ firstArgValue );
106
+
107
+ if ($ firstValueType instanceof ConstantStringType) {
108
+ $ resolvedType = $ this ->resolveConstantStringType ($ firstValueType , $ isForceArray );
109
+ } else {
110
+ $ resolvedType = new MixedType ();
111
+ }
112
+
113
+ // prefer specific type
114
+ if (! $ resolvedType instanceof MixedType) {
115
+ return $ resolvedType ;
116
+ }
117
+
118
+ // fallback type
119
+ if ($ isForceArray ) {
120
+ return new UnionType ([
121
+ new ArrayType (new MixedType (), new MixedType ()),
122
+ new StringType (),
123
+ new FloatType (),
124
+ new IntegerType (),
125
+ new BooleanType (),
126
+ ]);
127
+ }
128
+
129
+ // scalar types with stdClass
130
+ return new UnionType ([
131
+ new ObjectType (stdClass::class),
132
+ new StringType (),
133
+ new FloatType (),
134
+ new IntegerType (),
135
+ new BooleanType (),
136
+ ]);
137
+ }
138
+
139
+ /**
140
+ * Is "json_decode(..., true)"?
141
+ * @param Arg[] $args
142
+ */
143
+ private function isForceArray (FuncCall $ funcCall ): bool
144
+ {
145
+ $ args = $ funcCall ->getArgs ();
146
+
147
+ if (!isset ($ args [1 ])) {
148
+ return false ;
149
+ }
150
+
151
+ $ secondArgValue = $ args [1 ]->value ;
152
+ if ($ secondArgValue instanceof ConstFetch) {
153
+ if ($ secondArgValue ->name ->toLowerString () === 'true ' ) {
154
+ return true ;
155
+ }
156
+ }
157
+
158
+ return false ;
159
+ }
160
+
161
+ private function resolveConstantStringType (ConstantStringType $ constantStringType , bool $ isForceArray ): Type
162
+ {
163
+ $ decodedValue = json_decode ($ constantStringType ->getValue (), $ isForceArray );
164
+
165
+ return ConstantTypeHelper::getTypeFromValue ($ decodedValue );
166
+ }
167
+
68
168
}
0 commit comments