Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DynamicFunctionReturnTypeExtension for the get_debug_type function.
- Loading branch information
1 parent
6320e1d
commit 17d6626
Showing
6 changed files
with
184 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Type\Php; | ||
|
||
use Closure; | ||
use PhpParser\Node\Expr\FuncCall; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Reflection\FunctionReflection; | ||
use PHPStan\Type\Constant\ConstantStringType; | ||
use PHPStan\Type\DynamicFunctionReturnTypeExtension; | ||
use PHPStan\Type\StringType; | ||
use PHPStan\Type\Type; | ||
use PHPStan\Type\UnionType; | ||
use function array_map; | ||
use function count; | ||
|
||
class GetDebugTypeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension | ||
{ | ||
|
||
public function isFunctionSupported(FunctionReflection $functionReflection): bool | ||
{ | ||
return $functionReflection->getName() === 'get_debug_type'; | ||
} | ||
|
||
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type | ||
{ | ||
$argType = $scope->getType($functionCall->getArgs()[0]->value); | ||
if ($argType instanceof UnionType) { | ||
return new UnionType(array_map(Closure::fromCallable([self::class, 'resolveOneType']), $argType->getTypes())); | ||
} | ||
return self::resolveOneType($argType); | ||
} | ||
|
||
/** | ||
* @see https://www.php.net/manual/en/function.get-debug-type.php#refsect1-function.get-debug-type-returnvalues | ||
*/ | ||
private static function resolveOneType(Type $type): Type | ||
{ | ||
if ($type->isNull()->yes()) { | ||
return new ConstantStringType('null'); | ||
} | ||
if ($type->isBoolean()->yes()) { | ||
return new ConstantStringType('bool'); | ||
} | ||
if ($type->isInteger()->yes()) { | ||
return new ConstantStringType('int'); | ||
} | ||
if ($type->isFloat()->yes()) { | ||
return new ConstantStringType('float'); | ||
} | ||
if ($type->isString()->yes()) { | ||
return new ConstantStringType('string'); | ||
} | ||
if ($type->isArray()->yes()) { | ||
return new ConstantStringType('array'); | ||
} | ||
|
||
// "resources" type+state is skipped since we cannot infer the state | ||
|
||
if ($type->isObject()->yes()) { | ||
$classNames = $type->getObjectClassNames(); | ||
$reflections = $type->getObjectClassReflections(); | ||
|
||
$types = []; | ||
foreach ($classNames as $index => $className) { | ||
// if the class is not final, the actual returned string might be of a child class | ||
if ($reflections[$index]->isFinal() && !$reflections[$index]->isAnonymous()) { | ||
$types[] = new ConstantStringType($className); | ||
} | ||
|
||
if ($reflections[$index]->isAnonymous()) { // phpcs:ignore | ||
$types[] = new ConstantStringType('class@anonymous'); | ||
} | ||
} | ||
|
||
switch (count($types)) { | ||
case 0: | ||
return new StringType(); | ||
case 1: | ||
return $types[0]; | ||
default: | ||
return new UnionType($types); | ||
} | ||
} | ||
|
||
return new StringType(); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?php | ||
|
||
namespace GetDebugType; | ||
|
||
use function PHPStan\Testing\assertType; | ||
|
||
final class A {} | ||
|
||
/** | ||
* @param double $d | ||
* @param resource $r | ||
* @param int|string $intOrString | ||
* @param array|A $arrayOrObject | ||
*/ | ||
function doFoo(bool $b, int $i, float $f, $d, $r, string $s, array $a, $intOrString, $arrayOrObject) { | ||
$null = null; | ||
$resource = fopen('php://memory', 'r'); | ||
$o = new \stdClass(); | ||
$A = new A(); | ||
$anonymous = new class {}; | ||
|
||
assertType("'bool'", get_debug_type($b)); | ||
assertType("'bool'", get_debug_type(true)); | ||
assertType("'bool'", get_debug_type(false)); | ||
assertType("'int'", get_debug_type($i)); | ||
assertType("'float'", get_debug_type($f)); | ||
assertType("'float'", get_debug_type($d)); | ||
assertType("'string'", get_debug_type($s)); | ||
assertType("'array'", get_debug_type($a)); | ||
assertType("string", get_debug_type($o)); | ||
assertType("'GetDebugType\\\\A'", get_debug_type($A)); | ||
assertType("string", get_debug_type($r)); | ||
assertType("'bool'|string", get_debug_type($resource)); | ||
assertType("'null'", get_debug_type($null)); | ||
assertType("'int'|'string'", get_debug_type($intOrString)); | ||
assertType("'array'|'GetDebugType\\\\A'", get_debug_type($arrayOrObject)); | ||
assertType("'class@anonymous'", get_debug_type($anonymous)); | ||
} | ||
|
||
/** | ||
* @param non-empty-string $nonEmptyString | ||
* @param non-falsy-string $falsyString | ||
* @param numeric-string $numericString | ||
* @param class-string $classString | ||
*/ | ||
function strings($nonEmptyString, $falsyString, $numericString, $classString) { | ||
assertType("'string'", get_debug_type($nonEmptyString)); | ||
assertType("'string'", get_debug_type($falsyString)); | ||
assertType("'string'", get_debug_type($numericString)); | ||
assertType("'string'", get_debug_type($classString)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
tests/PHPStan/Rules/Comparison/data/match-get-debug-type.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
declare(strict_types = 1); | ||
|
||
namespace MatchGetDebugType; | ||
|
||
final class CustomError {} | ||
|
||
final class CustomResult {} | ||
|
||
class UnderTest { | ||
|
||
public static function getResultOrError(): CustomError|CustomResult | ||
{ | ||
|
||
} | ||
|
||
public static function matchOnResult(): bool | ||
{ | ||
return match (get_debug_type(self::getResultOrError())) { | ||
CustomError::class => false, | ||
CustomResult::class => true, | ||
}; | ||
} | ||
|
||
} |