1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

4.x - add support for get_debug_type function

Ref #4089
This commit is contained in:
Matt Brown 2020-10-02 19:15:47 -04:00
parent 74934ffdbb
commit 5bcd1bbb75
5 changed files with 192 additions and 7 deletions

View File

@ -140,6 +140,15 @@ class SwitchCaseAnalyzer
),
]
);
} elseif ($type instanceof Type\Atomic\TDependentGetDebugType) {
$type_statements[] = new PhpParser\Node\Expr\FuncCall(
new PhpParser\Node\Name(['get_debug_type']),
[
new PhpParser\Node\Arg(
new PhpParser\Node\Expr\Variable(substr($type->typeof, 1))
),
]
);
} else {
$type_statements = null;
break;

View File

@ -568,6 +568,7 @@ class AssertionFinder
$true_position = self::hasTrueVariable($conditional);
$empty_array_position = self::hasEmptyArrayVariable($conditional);
$gettype_position = self::hasGetTypeCheck($conditional);
$get_debug_type_position = self::hasGetDebugTypeCheck($conditional);
$min_count = null;
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
@ -965,6 +966,52 @@ class AssertionFinder
return $if_types;
}
if ($get_debug_type_position) {
if ($get_debug_type_position === self::ASSIGNMENT_TO_RIGHT) {
$whichclass_expr = $conditional->left;
$get_debug_type_expr = $conditional->right;
} elseif ($get_debug_type_position === self::ASSIGNMENT_TO_LEFT) {
$whichclass_expr = $conditional->right;
$get_debug_type_expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$gettype_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $get_debug_type_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$get_debug_type_expr->args[0]->value,
$this_class_name,
$source
);
if ($whichclass_expr instanceof PhpParser\Node\Scalar\String_) {
$var_type = $whichclass_expr->value;
} elseif ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch
&& $whichclass_expr->class instanceof PhpParser\Node\Name
) {
$var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
$whichclass_expr->class,
$source->getAliases()
);
} else {
throw new \UnexpectedValueException('Shouldnt get here');
}
if ($var_name && $var_type) {
if ($var_type === 'class@anonymous') {
$if_types[$var_name] = [['=object']];
} elseif ($var_type === 'resource (closed)') {
$if_types[$var_name] = [['closed-resource']];
} elseif (substr($var_type, 0, 10) === 'resource (') {
$if_types[$var_name] = [['=resource']];
} else {
$if_types[$var_name] = [[$var_type]];
}
}
return $if_types;
}
if ($count_equality_position) {
if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
$count_expr = $conditional->left;
@ -1206,6 +1253,7 @@ class AssertionFinder
$true_position = self::hasTrueVariable($conditional);
$empty_array_position = self::hasEmptyArrayVariable($conditional);
$gettype_position = self::hasGetTypeCheck($conditional);
$get_debug_type_position = self::hasGetDebugTypeCheck($conditional);
$count = null;
$count_inequality_position = self::hasNotCountEqualityCheck($conditional, $count);
@ -1631,6 +1679,52 @@ class AssertionFinder
return $if_types;
}
if ($get_debug_type_position) {
if ($get_debug_type_position === self::ASSIGNMENT_TO_RIGHT) {
$whichclass_expr = $conditional->left;
$get_debug_type_expr = $conditional->right;
} elseif ($get_debug_type_position === self::ASSIGNMENT_TO_LEFT) {
$whichclass_expr = $conditional->right;
$get_debug_type_expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$gettype_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $get_debug_type_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$get_debug_type_expr->args[0]->value,
$this_class_name,
$source
);
if ($whichclass_expr instanceof PhpParser\Node\Scalar\String_) {
$var_type = $whichclass_expr->value;
} elseif ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch
&& $whichclass_expr->class instanceof PhpParser\Node\Name
) {
$var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
$whichclass_expr->class,
$source->getAliases()
);
} else {
throw new \UnexpectedValueException('Shouldnt get here');
}
if ($var_name && $var_type) {
if ($var_type === 'class@anonymous') {
$if_types[$var_name] = [['!=object']];
} elseif ($var_type === 'resource (closed)') {
$if_types[$var_name] = [['!closed-resource']];
} elseif (substr($var_type, 0, 10) === 'resource (') {
$if_types[$var_name] = [['!=resource']];
} else {
$if_types[$var_name] = [['!' . $var_type]];
}
}
return $if_types;
}
if (!$source instanceof StatementsAnalyzer) {
return [];
}
@ -2622,6 +2716,32 @@ class AssertionFinder
return false;
}
/**
* @return false|int
*/
protected static function hasGetDebugTypeCheck(PhpParser\Node\Expr\BinaryOp $conditional)
{
if ($conditional->right instanceof PhpParser\Node\Expr\FuncCall
&& $conditional->right->name instanceof PhpParser\Node\Name
&& strtolower($conditional->right->name->parts[0]) === 'get_debug_type'
&& $conditional->right->args
&& $conditional->left instanceof PhpParser\Node\Scalar\String_
) {
return self::ASSIGNMENT_TO_RIGHT;
}
if ($conditional->left instanceof PhpParser\Node\Expr\FuncCall
&& $conditional->left->name instanceof PhpParser\Node\Name
&& strtolower($conditional->left->name->parts[0]) === 'get_debug_type'
&& $conditional->left->args
&& $conditional->right instanceof PhpParser\Node\Scalar\String_
) {
return self::ASSIGNMENT_TO_LEFT;
}
return false;
}
/**
* @return false|int
*/

View File

@ -1327,7 +1327,10 @@ class FunctionCallAnalyzer extends CallAnalyzer
) : void {
$first_arg = isset($stmt->args[0]) ? $stmt->args[0] : null;
if ($function_name->parts === ['get_class'] || $function_name->parts === ['gettype']) {
if ($function_name->parts === ['get_class']
|| $function_name->parts === ['gettype']
|| $function_name->parts === ['get_debug_type']
) {
if ($first_arg) {
$var = $first_arg->value;
@ -1337,18 +1340,26 @@ class FunctionCallAnalyzer extends CallAnalyzer
$var_id = '$' . $var->name;
if (isset($context->vars_in_scope[$var_id])) {
$atomic_type = $function_name->parts === ['get_class']
? new Type\Atomic\TDependentGetClass(
if ($function_name->parts === ['get_class']) {
$atomic_type = new Type\Atomic\TDependentGetClass(
$var_id,
$context->vars_in_scope[$var_id]->hasMixed()
? Type::getObject()
: $context->vars_in_scope[$var_id]
)
: new Type\Atomic\TDependentGetType($var_id);
);
} elseif ($function_name->parts === ['get_class']) {
$atomic_type = new Type\Atomic\TDependentGetType($var_id);
} else {
$atomic_type = new Type\Atomic\TDependentGetDebugType($var_id);
}
$statements_analyzer->node_data->setType($real_stmt, new Type\Union([$atomic_type]));
}
} elseif ($var_type = $statements_analyzer->node_data->getType($var)) {
} elseif (($var_type = $statements_analyzer->node_data->getType($var))
&& ($function_name->parts === ['get_class']
|| $function_name->parts === ['get_debug_type']
)
) {
$class_string_types = [];
foreach ($var_type->getAtomicTypes() as $class_type) {
@ -1374,8 +1385,24 @@ class FunctionCallAnalyzer extends CallAnalyzer
$class_type->defining_class
);
}
} else {
} elseif ($function_name->parts === ['get_class']) {
$class_string_types[] = new Type\Atomic\TClassString();
} elseif ($function_name->parts === ['get_debug_type']) {
if ($class_type instanceof Type\Atomic\TInt) {
$class_string_types[] = new Type\Atomic\TLiteralString('int');
} elseif ($class_type instanceof Type\Atomic\TString) {
$class_string_types[] = new Type\Atomic\TLiteralString('string');
} elseif ($class_type instanceof Type\Atomic\TFloat) {
$class_string_types[] = new Type\Atomic\TLiteralString('float');
} elseif ($class_type instanceof Type\Atomic\TBool) {
$class_string_types[] = new Type\Atomic\TLiteralString('bool');
} elseif ($class_type instanceof Type\Atomic\TClosedResource) {
$class_string_types[] = new Type\Atomic\TLiteralString('resource (closed)');
} elseif ($class_type instanceof Type\Atomic\TNull) {
$class_string_types[] = new Type\Atomic\TLiteralString('null');
} else {
$class_string_types[] = new Type\Atomic\TString();
}
}
}

View File

@ -3617,6 +3617,7 @@ return [
'get_declared_classes' => ['list<class-string>'],
'get_declared_interfaces' => ['list<class-string>'],
'get_declared_traits' => ['list<class-string>|null'],
'get_debug_type' => ['string', 'data'=>'mixed'],
'get_defined_constants' => ['array<string,int|string|float|bool|null|array|resource>', 'categorize='=>'bool'],
'get_defined_functions' => ['array<string,array<string,callable-string>>', 'exclude_disabled='=>'bool'],
'get_defined_vars' => ['array'],

View File

@ -0,0 +1,28 @@
<?php
namespace Psalm\Type\Atomic;
/**
* Represents a string whose value is that of a type found by get_debug_type($var)
*/
class TDependentGetDebugType extends TString
{
/**
* Used to hold information as to what this refers to
*
* @var string
*/
public $typeof;
/**
* @param string $typeof the variable id
*/
public function __construct(string $typeof)
{
$this->typeof = $typeof;
}
public function canBeFullyExpressedInPhp(): bool
{
return false;
}
}