Skip to content

Commit 1a124ea

Browse files
TomasVotrubaondrejmirtes
authored andcommitted
decopule type resolution to static
1 parent 66e3aa0 commit 1a124ea

File tree

2 files changed

+58
-21
lines changed

2 files changed

+58
-21
lines changed

src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33
namespace PHPStan\Type\Php;
44

5+
<<<<<<< HEAD
56
<<<<<<< HEAD
67
<<<<<<< HEAD
78
=======
89
use PhpParser\Node\Arg;
910
=======
11+
=======
12+
use PhpParser\ConstExprEvaluationException;
13+
>>>>>>> decopule type resolution to static
1014
use PhpParser\ConstExprEvaluator;
1115
>>>>>>> check for JSON_OBJECT_AS_ARRAY, in case of null and array
1216
use PhpParser\Node\Expr;
@@ -40,23 +44,39 @@
4044
use PHPStan\Type\TypeCombinator;
4145
use stdClass;
4246
use function constant;
47+
use function is_bool;
4348
use function json_decode;
4449
use const JSON_OBJECT_AS_ARRAY;
4550

4651
class JsonThrowOnErrorDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
4752
{
4853

54+
private const UNABLE_TO_RESOLVE = '__UNABLE_TO_RESOLVE__';
55+
4956
/** @var array<string, int> */
5057
private array $argumentPositions = [
5158
'json_encode' => 1,
5259
'json_decode' => 3,
5360
];
5461

62+
<<<<<<< HEAD
5563
public function __construct(
5664
private ReflectionProvider $reflectionProvider,
5765
private BitwiseFlagHelper $bitwiseFlagAnalyser,
5866
)
67+
=======
68+
private ConstExprEvaluator $constExprEvaluator;
69+
70+
public function __construct(private ReflectionProvider $reflectionProvider)
71+
>>>>>>> decopule type resolution to static
5972
{
73+
$this->constExprEvaluator = new ConstExprEvaluator(static function (Expr $expr) {
74+
if ($expr instanceof ConstFetch) {
75+
return constant($expr->name->toString());
76+
}
77+
78+
return null;
79+
});
6080
}
6181

6282
public function isFunctionSupported(
@@ -100,7 +120,7 @@ public function getTypeFromFunctionCall(
100120
private function narrowTypeForJsonDecode(FuncCall $funcCall, Scope $scope): Type
101121
{
102122
$args = $funcCall->getArgs();
103-
$isArrayWithoutStdClass = $this->isForceArrayWithoutStdClass($funcCall);
123+
$isArrayWithoutStdClass = $this->isForceArrayWithoutStdClass($funcCall, $scope);
104124

105125
$firstArgValue = $args[0]->value;
106126
$firstValueType = $scope->getType($firstArgValue);
@@ -110,7 +130,7 @@ private function narrowTypeForJsonDecode(FuncCall $funcCall, Scope $scope): Type
110130
}
111131

112132
// fallback type
113-
if ($isArrayWithoutStdClass) {
133+
if ($isArrayWithoutStdClass === true) {
114134
return new MixedType(true, new ObjectType(stdClass::class));
115135
}
116136

@@ -120,39 +140,30 @@ private function narrowTypeForJsonDecode(FuncCall $funcCall, Scope $scope): Type
120140
/**
121141
* Is "json_decode(..., true)"?
122142
*/
123-
private function isForceArrayWithoutStdClass(FuncCall $funcCall): bool
143+
private function isForceArrayWithoutStdClass(FuncCall $funcCall, Scope $scope): bool
124144
{
125145
$args = $funcCall->getArgs();
126146

127-
$constExprEvaluator = new ConstExprEvaluator(static function (Expr $expr) {
128-
if ($expr instanceof ConstFetch) {
129-
return constant($expr->name->toString());
130-
}
131-
132-
return null;
133-
});
134-
135147
if (isset($args[1])) {
136-
$secondArgValue = $args[1]->value;
137-
138-
$constValue = $constExprEvaluator->evaluateSilently($secondArgValue);
139-
if ($constValue === true) {
140-
return true;
148+
$secondArgValue = $this->resolveMaskValue($args[1]->value, $scope);
149+
if ($secondArgValue === self::UNABLE_TO_RESOLVE) {
150+
return false;
141151
}
142152

143-
if ($constValue === false) {
144-
return false;
153+
if (is_bool($secondArgValue)) {
154+
return $secondArgValue;
145155
}
146156

147157
// depends on used constants
148-
if ($constValue === null) {
158+
if ($secondArgValue === null) {
149159
if (! isset($args[3])) {
150160
return false;
151161
}
152162

153163
// @see https://www.php.net/manual/en/json.constants.php#constant.json-object-as-array
154-
$thirdArgValue = $constExprEvaluator->evaluateSilently($args[3]->value);
155-
if ($thirdArgValue & JSON_OBJECT_AS_ARRAY) {
164+
$thirdArgValue = $args[3]->value;
165+
$resolvedThirdArgValue = $this->resolveMaskValue($thirdArgValue, $scope);
166+
if (($resolvedThirdArgValue & JSON_OBJECT_AS_ARRAY) !== 0) {
156167
return true;
157168
}
158169
}
@@ -168,4 +179,23 @@ private function resolveConstantStringType(ConstantStringType $constantStringTyp
168179
return ConstantTypeHelper::getTypeFromValue($decodedValue);
169180
}
170181

182+
/**
183+
* @return mixed
184+
*/
185+
private function resolveMaskValue(Expr $expr, Scope $scope)
186+
{
187+
$thirdArgValueType = $scope->getType($expr);
188+
if ($thirdArgValueType instanceof ConstantIntegerType) {
189+
return $thirdArgValueType->getValue();
190+
}
191+
192+
// fallback to value resolver
193+
try {
194+
return $this->constExprEvaluator->evaluateSilently($expr);
195+
} catch (ConstExprEvaluationException) {
196+
// unable to resolve
197+
return self::UNABLE_TO_RESOLVE;
198+
}
199+
}
200+
171201
}

tests/PHPStan/Analyser/data/json-decode/json_object_as_array.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ function ($mixed) {
1010
assertType('mixed~stdClass', $value);
1111
};
1212

13+
function ($mixed) {
14+
$flagsAsVariable = JSON_OBJECT_AS_ARRAY;
15+
16+
$value = json_decode($mixed, null, 512, $flagsAsVariable);
17+
assertType('mixed~stdClass', $value);
18+
};
19+
1320
function ($mixed) {
1421
$value = json_decode($mixed, null, 512, JSON_OBJECT_AS_ARRAY | JSON_BIGINT_AS_STRING);
1522
assertType('mixed~stdClass', $value);

0 commit comments

Comments
 (0)