Skip to content

Commit 47364df

Browse files
authored
Replace instanceof checks in JsonThrowOnErrorDynamicReturnTypeExtension
1 parent ff2ce20 commit 47364df

File tree

4 files changed

+71
-28
lines changed

4 files changed

+71
-28
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,18 +1605,6 @@ parameters:
16051605
count: 2
16061606
path: src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php
16071607

1608-
-
1609-
rawMessage: 'Doing instanceof PHPStan\Type\ConstantScalarType is error-prone and deprecated. Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues() instead.'
1610-
identifier: phpstanApi.instanceofType
1611-
count: 1
1612-
path: src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php
1613-
1614-
-
1615-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
1616-
identifier: phpstanApi.instanceofType
1617-
count: 1
1618-
path: src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php
1619-
16201608
-
16211609
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
16221610
identifier: phpstanApi.instanceofType

src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
use PHPStan\Reflection\FunctionReflection;
1010
use PHPStan\Reflection\ParametersAcceptorSelector;
1111
use PHPStan\Reflection\ReflectionProvider;
12+
use PHPStan\TrinaryLogic;
1213
use PHPStan\Type\BitwiseFlagHelper;
1314
use PHPStan\Type\Constant\ConstantBooleanType;
1415
use PHPStan\Type\Constant\ConstantStringType;
15-
use PHPStan\Type\ConstantScalarType;
1616
use PHPStan\Type\ConstantTypeHelper;
1717
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
1818
use PHPStan\Type\ObjectWithoutClassType;
@@ -87,11 +87,17 @@ private function narrowTypeForJsonDecode(FuncCall $funcCall, Scope $scope, Type
8787
}
8888

8989
$firstValueType = $scope->getType($args[0]->value);
90-
if ($firstValueType instanceof ConstantStringType) {
91-
return $this->resolveConstantStringType($firstValueType, $isForceArray);
90+
if ($firstValueType->getConstantStrings() !== []) {
91+
$types = [];
92+
93+
foreach ($firstValueType->getConstantStrings() as $constantString) {
94+
$types[] = $this->resolveConstantStringType($constantString, $isForceArray);
95+
}
96+
97+
return TypeCombinator::union(...$types);
9298
}
9399

94-
if ($isForceArray) {
100+
if ($isForceArray->yes()) {
95101
return TypeCombinator::remove($fallbackType, new ObjectWithoutClassType());
96102
}
97103

@@ -101,33 +107,55 @@ private function narrowTypeForJsonDecode(FuncCall $funcCall, Scope $scope, Type
101107
/**
102108
* Is "json_decode(..., true)"?
103109
*/
104-
private function isForceArray(FuncCall $funcCall, Scope $scope): bool
110+
private function isForceArray(FuncCall $funcCall, Scope $scope): TrinaryLogic
105111
{
106112
$args = $funcCall->getArgs();
113+
$flagValue = $this->getFlagValue($funcCall, $scope);
107114
if (!isset($args[1])) {
108-
return false;
115+
return TrinaryLogic::createNo();
109116
}
110117

111118
$secondArgType = $scope->getType($args[1]->value);
112-
$secondArgValue = $secondArgType instanceof ConstantScalarType ? $secondArgType->getValue() : null;
119+
$secondArgValues = [];
120+
foreach ($secondArgType->getConstantScalarValues() as $value) {
121+
if ($value === null) {
122+
$secondArgValues[] = $flagValue;
123+
continue;
124+
}
125+
if (!is_bool($value)) {
126+
return TrinaryLogic::createNo();
127+
}
128+
$secondArgValues[] = TrinaryLogic::createFromBoolean($value);
129+
}
113130

114-
if (is_bool($secondArgValue)) {
115-
return $secondArgValue;
131+
if ($secondArgValues === []) {
132+
return TrinaryLogic::createNo();
116133
}
117134

118-
if ($secondArgValue !== null || !isset($args[3])) {
119-
return false;
135+
return TrinaryLogic::extremeIdentity(...$secondArgValues);
136+
}
137+
138+
private function resolveConstantStringType(ConstantStringType $constantStringType, TrinaryLogic $isForceArray): Type
139+
{
140+
$types = [];
141+
/** @var bool $asArray */
142+
foreach ($isForceArray->toBooleanType()->getConstantScalarValues() as $asArray) {
143+
$decodedValue = json_decode($constantStringType->getValue(), $asArray);
144+
$types[] = ConstantTypeHelper::getTypeFromValue($decodedValue);
120145
}
121146

122-
// depends on used constants, @see https://www.php.net/manual/en/json.constants.php#constant.json-object-as-array
123-
return $this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($args[3]->value, $scope, 'JSON_OBJECT_AS_ARRAY')->yes();
147+
return TypeCombinator::union(...$types);
124148
}
125149

126-
private function resolveConstantStringType(ConstantStringType $constantStringType, bool $isForceArray): Type
150+
private function getFlagValue(FuncCall $funcCall, Scope $scope): TrinaryLogic
127151
{
128-
$decodedValue = json_decode($constantStringType->getValue(), $isForceArray);
152+
$args = $funcCall->getArgs();
153+
if (!isset($args[3])) {
154+
return TrinaryLogic::createNo();
155+
}
129156

130-
return ConstantTypeHelper::getTypeFromValue($decodedValue);
157+
// depends on used constants, @see https://www.php.net/manual/en/json.constants.php#constant.json-object-as-array
158+
return $this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($args[3]->value, $scope, 'JSON_OBJECT_AS_ARRAY');
131159
}
132160

133161
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,10 @@ function ($mixed, $unknownFlags) {
3131
$value = json_decode($mixed, null, 512, $unknownFlags);
3232
assertType('mixed', $value);
3333
};
34+
35+
function(string $json, ?bool $asArray): void {
36+
/** @var '{}'|'null' $json */
37+
38+
$value = json_decode($json, null, 512, JSON_OBJECT_AS_ARRAY);
39+
assertType('array{}|null', $value);
40+
};

tests/PHPStan/Analyser/nsrt/json-decode/narrow_type.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,23 @@ function ($mixed) {
3232
$value = json_decode($mixed, false);
3333
assertType('mixed', $value);
3434
};
35+
36+
function ($mixed, $asArray) {
37+
$value = json_decode($mixed, $asArray);
38+
assertType('mixed', $value);
39+
};
40+
41+
function(string $json, ?bool $asArray): void {
42+
/** @var '{}'|'null' $json */
43+
$value = json_decode($json);
44+
assertType('stdClass|null', $value);
45+
46+
$value = json_decode($json, true);
47+
assertType('array{}|null', $value);
48+
49+
$value = json_decode($json, $asArray);
50+
assertType('array{}|stdClass|null', $value);
51+
52+
$value = json_decode($json, 'foo');
53+
assertType('stdClass|null', $value);
54+
};

0 commit comments

Comments
 (0)