Skip to content

Commit 4d80fb2

Browse files
committed
call phpstan extension instead of copying it
1 parent 4e46d4b commit 4d80fb2

File tree

3 files changed

+11
-84
lines changed

3 files changed

+11
-84
lines changed

phpstan.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ parameters:
2626
count: 1
2727
path: src/Rules/Error/SafeRuleError.php
2828
-
29-
message: '#^Calling PHPStan\\Type\\BitwiseFlagHelper::bitwiseOrContainsConstant\(\) is not covered by backward compatibility promise\. The method might change in a minor PHPStan version\.$#'
29+
message: '#^Calling PHPStan\\Type\\Php\\JsonThrowOnErrorDynamicReturnTypeExtension\:\:getTypeFromFunctionCall\(\) is not covered by backward compatibility promise\. The method might change in a minor PHPStan version\.$#'
3030
identifier: phpstanApi.method
3131
count: 1
3232
path: src/Type/Php/JsonDecodeDynamicReturnTypeExtension.php

src/Type/Php/JsonDecodeDynamicReturnTypeExtension.php

Lines changed: 10 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,23 @@
44
namespace TheCodingMachine\Safe\PHPStan\Type\Php;
55

66
use PhpParser\Node\Expr\FuncCall;
7+
use PhpParser\Node\Name;
78
use PHPStan\Analyser\Scope;
89
use PHPStan\Reflection\FunctionReflection;
9-
use PHPStan\Reflection\ParametersAcceptorSelector;
10-
use PHPStan\Type\BitwiseFlagHelper;
11-
use PHPStan\Type\Constant\ConstantStringType;
12-
use PHPStan\Type\ConstantScalarType;
13-
use PHPStan\Type\ConstantTypeHelper;
10+
use PHPStan\Reflection\ReflectionProvider;
1411
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
1512
use PHPStan\Type\NeverType;
16-
use PHPStan\Type\ObjectWithoutClassType;
13+
use PHPStan\Type\Php\JsonThrowOnErrorDynamicReturnTypeExtension;
1714
use PHPStan\Type\Type;
18-
use PHPStan\Type\TypeCombinator;
19-
use Safe\Exceptions\JsonException;
2015

2116
/**
2217
* @see \PHPStan\Type\Php\JsonThrowOnErrorDynamicReturnTypeExtension
2318
*/
2419
final class JsonDecodeDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
2520
{
2621
public function __construct(
27-
private readonly BitwiseFlagHelper $bitwiseFlagAnalyser,
22+
private readonly JsonThrowOnErrorDynamicReturnTypeExtension $phpstanCheck,
23+
private readonly ReflectionProvider $reflectionProvider,
2824
) {
2925
}
3026

@@ -35,74 +31,14 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
3531

3632
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
3733
{
38-
$defaultReturnType = ParametersAcceptorSelector::selectFromArgs(
39-
$scope,
40-
$functionCall->getArgs(),
41-
$functionReflection->getVariants(),
42-
)->getReturnType();
34+
$functionReflection = $this->reflectionProvider->getFunction(new Name('json_decode'), null);
35+
$result = $this->phpstanCheck->getTypeFromFunctionCall($functionReflection, $functionCall, $scope);
4336

44-
return $this->narrowTypeForJsonDecode($functionCall, $scope, $defaultReturnType);
45-
}
46-
47-
private function narrowTypeForJsonDecode(FuncCall $funcCall, Scope $scope, Type $fallbackType): Type
48-
{
49-
$args = $funcCall->getArgs();
50-
$isForceArray = $this->isForceArray($funcCall, $scope);
51-
if (!isset($args[0])) {
52-
return $fallbackType;
53-
}
54-
55-
$firstValueType = $scope->getType($args[0]->value);
56-
if ([] !== $firstValueType->getConstantStrings()) {
57-
$types = [];
58-
59-
foreach ($firstValueType->getConstantStrings() as $constantString) {
60-
$types[] = $this->resolveConstantStringType($constantString, $isForceArray);
61-
}
62-
63-
return TypeCombinator::union(...$types);
64-
}
65-
66-
if ($isForceArray) {
67-
return TypeCombinator::remove($fallbackType, new ObjectWithoutClassType());
68-
}
69-
70-
return $fallbackType;
71-
}
72-
73-
/**
74-
* Is "json_decode(..., true)"?
75-
*/
76-
private function isForceArray(FuncCall $funcCall, Scope $scope): bool
77-
{
78-
$args = $funcCall->getArgs();
79-
if (!isset($args[1])) {
80-
return false;
81-
}
82-
83-
$secondArgType = $scope->getType($args[1]->value);
84-
$secondArgValue = 1 === \count($secondArgType->getConstantScalarValues()) ? $secondArgType->getConstantScalarValues()[0] : null;
85-
86-
if (is_bool($secondArgValue)) {
87-
return $secondArgValue;
88-
}
89-
90-
if ($secondArgValue !== null || !isset($args[3])) {
91-
return false;
92-
}
93-
94-
// depends on used constants, @see https://www.php.net/manual/en/json.constants.php#constant.json-object-as-array
95-
return $this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($args[3]->value, $scope, 'JSON_OBJECT_AS_ARRAY')->yes();
96-
}
97-
98-
private function resolveConstantStringType(ConstantStringType $constantStringType, bool $isForceArray): Type
99-
{
100-
try {
101-
$decodedValue = \Safe\json_decode($constantStringType->getValue(), $isForceArray);
102-
} catch (JsonException) {
37+
// if PHPStan reports null and there is a json error, then an invalid constant string was passed
38+
if ($result->isNull()->yes() && JSON_ERROR_NONE !== json_last_error()) {
10339
return new NeverType();
10440
}
10541

106-
return ConstantTypeHelper::getTypeFromValue($decodedValue);
42+
return $result;
10743
}
10844
}

tests/Type/Php/data/json_decode_return.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,3 @@ function(string $json): void {
3333
$value = \Safe\json_decode($json, true);
3434
\PHPStan\Testing\assertType('mixed~object', $value);
3535
};
36-
37-
function(string $json): void {
38-
/** @var '{}'|'null' $json */
39-
$value = \Safe\json_decode($json);
40-
\PHPStan\Testing\assertType('stdClass|null', $value);
41-
42-
$value = \Safe\json_decode($json, true);
43-
\PHPStan\Testing\assertType('array{}|null', $value);
44-
};

0 commit comments

Comments
 (0)