Skip to content

Commit

Permalink
decopule type resolution to static
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Mar 24, 2022
1 parent 3066b95 commit 6a20ab8
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 20 deletions.
67 changes: 47 additions & 20 deletions src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

namespace PHPStan\Type\Php;

<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
use PhpParser\Node\Arg;
=======
=======
use PhpParser\ConstExprEvaluationException;
>>>>>>> decopule type resolution to static
use PhpParser\ConstExprEvaluator;
>>>>>>> check for JSON_OBJECT_AS_ARRAY, in case of null and array
use PhpParser\Node\Expr;
Expand Down Expand Up @@ -40,23 +44,39 @@
use PHPStan\Type\TypeCombinator;
use stdClass;
use function constant;
use function is_bool;
use function json_decode;
use const JSON_OBJECT_AS_ARRAY;
class JsonThrowOnErrorDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
{
private const UNABLE_TO_RESOLVE = '__UNABLE_TO_RESOLVE__';
/** @var array<string, int> */
private array $argumentPositions = [
'json_encode' => 1,
'json_decode' => 3,
];
<<<<<<< HEAD
public function __construct(
private ReflectionProvider $reflectionProvider,
private BitwiseFlagHelper $bitwiseFlagAnalyser,
)
=======
private ConstExprEvaluator $constExprEvaluator;

public function __construct(private ReflectionProvider $reflectionProvider)
>>>>>>> decopule type resolution to static
{
$this->constExprEvaluator = new ConstExprEvaluator(static function (Expr $expr) {
if ($expr instanceof ConstFetch) {
return constant($expr->name->toString());
}

return null;
});
}

public function isFunctionSupported(
Expand Down Expand Up @@ -101,7 +121,7 @@ public function getTypeFromFunctionCall(
private function narrowTypeForJsonDecode(FuncCall $funcCall, Scope $scope): Type
{
$args = $funcCall->getArgs();
$isArrayWithoutStdClass = $this->isForceArrayWithoutStdClass($funcCall);
$isArrayWithoutStdClass = $this->isForceArrayWithoutStdClass($funcCall, $scope);

$firstArgValue = $args[0]->value;
$firstValueType = $scope->getType($firstArgValue);
Expand All @@ -121,39 +141,30 @@ private function narrowTypeForJsonDecode(FuncCall $funcCall, Scope $scope): Type
/**
* Is "json_decode(..., true)"?
*/
private function isForceArrayWithoutStdClass(FuncCall $funcCall): bool
private function isForceArrayWithoutStdClass(FuncCall $funcCall, Scope $scope): bool
{
$args = $funcCall->getArgs();

$constExprEvaluator = new ConstExprEvaluator(static function (Expr $expr) {
if ($expr instanceof ConstFetch) {
return constant($expr->name->toString());
}

return null;
});

if (isset($args[1])) {
$secondArgValue = $args[1]->value;

$constValue = $constExprEvaluator->evaluateSilently($secondArgValue);
if ($constValue === true) {
return true;
$secondArgValue = $this->resolveMaskValue($args[1]->value, $scope);
if ($secondArgValue === self::UNABLE_TO_RESOLVE) {
return false;
}

if ($constValue === false) {
return false;
if (is_bool($secondArgValue)) {
return $secondArgValue;
}

// depends on used constants
if ($constValue === null) {
if ($secondArgValue === null) {
if (! isset($args[3])) {
return false;
}

// @see https://www.php.net/manual/en/json.constants.php#constant.json-object-as-array
$thirdArgValue = $constExprEvaluator->evaluateSilently($args[3]->value);
if ($thirdArgValue & JSON_OBJECT_AS_ARRAY) {
$thirdArgValue = $args[3]->value;
$resolvedThirdArgValue = $this->resolveMaskValue($thirdArgValue, $scope);
if ($resolvedThirdArgValue & JSON_OBJECT_AS_ARRAY) {
return true;
}
}
Expand All @@ -169,4 +180,20 @@ private function resolveConstantStringType(ConstantStringType $constantStringTyp
return ConstantTypeHelper::getTypeFromValue($decodedValue);
}

private function resolveMaskValue(Expr $expr, Scope $scope)
{
$thirdArgValueType = $scope->getType($expr);
if ($thirdArgValueType instanceof ConstantIntegerType) {
return $thirdArgValueType->getValue();
}

// fallback to value resolver
try {
return $this->constExprEvaluator->evaluateSilently($expr);
} catch (ConstExprEvaluationException) {
// unable to resolve
return self::UNABLE_TO_RESOLVE;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ function ($mixed) {
assertType('mixed~stdClass', $value);
};

function ($mixed) {
$flagsAsVariable = JSON_OBJECT_AS_ARRAY;

$value = json_decode($mixed, null, 512, $flagsAsVariable);
assertType('mixed~stdClass', $value);
};

function ($mixed) {
$value = json_decode($mixed, null, 512, JSON_OBJECT_AS_ARRAY | JSON_BIGINT_AS_STRING);
assertType('mixed~stdClass', $value);
Expand Down

0 comments on commit 6a20ab8

Please sign in to comment.