22
33namespace PHPStan \Type \Php ;
44
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
512use PhpParser \Node \Expr \FuncCall;
613use PhpParser \Node \Name \FullyQualified ;
714use PHPStan \Analyser \Scope ;
815use PHPStan \Reflection \FunctionReflection ;
916use PHPStan \Reflection \ParametersAcceptorSelector ;
1017use PHPStan \Reflection \ReflectionProvider ;
18+ <<<<<<< HEAD
1119use PHPStan \Type \BitwiseFlagHelper;
1220use 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
1329use 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 ;
1435use PHPStan \Type \Type ;
1536use PHPStan \Type \TypeCombinator ;
16- use function in_array ;
37+ use PHPStan \Type \UnionType ;
38+ use stdClass ;
39+ use function json_decode ;
1740
1841class JsonThrowOnErrorDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
1942{
@@ -35,14 +58,11 @@ public function isFunctionSupported(
3558 FunctionReflection $ functionReflection ,
3659 ): bool
3760 {
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 ' ;
4666 }
4767
4868 public function getTypeFromFunctionCall (
@@ -51,8 +71,19 @@ public function getTypeFromFunctionCall(
5171 Scope $ scope ,
5272 ): Type
5373 {
74+ // update type based on JSON_THROW_ON_ERROR
5475 $ argumentPosition = $ this ->argumentPositions [$ functionReflection ->getName ()];
5576 $ 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+
5687 if (!isset ($ functionCall ->getArgs ()[$ argumentPosition ])) {
5788 return $ defaultReturnType ;
5889 }
@@ -65,4 +96,73 @@ public function getTypeFromFunctionCall(
6596 return $ defaultReturnType ;
6697 }
6798
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+
68168}
0 commit comments