Skip to content

Commit

Permalink
Adds type-specifying extension for settype()
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisBrenton committed Feb 19, 2024
1 parent b026126 commit d2c30d1
Show file tree
Hide file tree
Showing 4 changed files with 770 additions and 0 deletions.
5 changes: 5 additions & 0 deletions conf/config.neon
Expand Up @@ -1529,6 +1529,11 @@ services:
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension

-
class: PHPStan\Type\Php\SetTypeFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension

-
class: PHPStan\Type\Php\StrCaseFunctionsReturnTypeExtension
tags:
Expand Down
91 changes: 91 additions & 0 deletions src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php
@@ -0,0 +1,91 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\ErrorType;
use PHPStan\Type\FunctionTypeSpecifyingExtension;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\TypeCombinator;
use stdClass;
use function count;
use function strtolower;

class SetTypeFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension
{

private TypeSpecifier $typeSpecifier;

public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool
{
return strtolower($functionReflection->getName()) === 'settype'
&& count($node->getArgs()) > 1
&& $context->null();
}

public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$value = $node->getArgs()[0]->value;
$valueType = $scope->getType($value);
$castType = $scope->getType($node->getArgs()[1]->value);

$constantStrings = $castType->getConstantStrings();
if (count($constantStrings) < 1) {
return new SpecifiedTypes();
}

$types = [];

foreach ($constantStrings as $constantString) {
switch ($constantString->getValue()) {
case 'bool':
case 'boolean':
$types[] = $valueType->toBoolean();
break;
case 'int':
case 'integer':
$types[] = $valueType->toInteger();
break;
case 'float':
case 'double':
$types[] = $valueType->toFloat();
break;
case 'string':
$types[] = $valueType->toString();
break;
case 'array':
$types[] = $valueType->toArray();
break;
case 'object':
$types[] = new ObjectType(stdClass::class);
break;
case 'null':
$types[] = new NullType();
break;
default:
$types[] = new ErrorType();
}
}

return $this->typeSpecifier->create(
$value,
TypeCombinator::union(...$types),
TypeSpecifierContext::createTruthy(),
true,
$scope,
);
}

public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
{
$this->typeSpecifier = $typeSpecifier;
}

}
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -1431,6 +1431,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/assert-inheritance.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9123.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10037.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/set-type-type-specifying.php');
}

/**
Expand Down

0 comments on commit d2c30d1

Please sign in to comment.