mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Improve checks for switch types
This commit is contained in:
parent
8a28ab4b26
commit
d1807cfb95
@ -52,6 +52,21 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
'mixed' => 'mixed',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $GETTYPE_TYPES = [
|
||||
'boolean' => true,
|
||||
'integer' => true,
|
||||
'double' => true,
|
||||
'string' => true,
|
||||
'array' => true,
|
||||
'object' => true,
|
||||
'resource' => true,
|
||||
'NULL' => true,
|
||||
'unknown type' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var PhpParser\Node\Stmt\ClassLike
|
||||
*/
|
||||
|
@ -2,12 +2,14 @@
|
||||
namespace Psalm\Checker\Statements\Block;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Checker\ClassLikeChecker;
|
||||
use Psalm\Checker\ScopeChecker;
|
||||
use Psalm\Checker\Statements\ExpressionChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\ContinueOutsideLoop;
|
||||
use Psalm\Issue\UnevaluatedCode;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
|
||||
@ -33,11 +35,14 @@ class SwitchChecker
|
||||
return false;
|
||||
}
|
||||
|
||||
$type_type = null;
|
||||
|
||||
if (isset($stmt->cond->inferredType) &&
|
||||
array_values($stmt->cond->inferredType->types)[0] instanceof Type\Atomic\T
|
||||
) {
|
||||
/** @var Type\Atomic\T */
|
||||
$type_type = array_values($stmt->cond->inferredType->types)[0];
|
||||
|
||||
$type_candidate_var = $type_type->typeof;
|
||||
}
|
||||
|
||||
@ -78,28 +83,55 @@ class SwitchChecker
|
||||
$case = $stmt->cases[$i];
|
||||
/** @var string */
|
||||
$case_exit_type = $case_exit_types[$i];
|
||||
$case_type = null;
|
||||
|
||||
$case_context = clone $original_context;
|
||||
$case_context->parent_context = $context;
|
||||
|
||||
if ($case->cond) {
|
||||
if (ExpressionChecker::analyze($statements_checker, $case->cond, $context) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($type_candidate_var && $case->cond instanceof PhpParser\Node\Scalar\String_) {
|
||||
$case_type = $case->cond->value;
|
||||
if ($type_type
|
||||
&& $type_candidate_var
|
||||
&& $case->cond instanceof PhpParser\Node\Scalar\String_
|
||||
) {
|
||||
$fq_classlike_name = $case->cond->value;
|
||||
|
||||
$file_checker = $statements_checker->getFileChecker();
|
||||
|
||||
if ($type_type instanceof Type\Atomic\GetClassT) {
|
||||
ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$file_checker->project_checker,
|
||||
$fq_classlike_name,
|
||||
new CodeLocation($file_checker, $case->cond),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
} elseif (!isset(ClassLikeChecker::$GETTYPE_TYPES[$fq_classlike_name])) {
|
||||
if (IssueBuffer::accepts(
|
||||
new UnevaluatedCode(
|
||||
'gettype cannot return this value',
|
||||
new CodeLocation($file_checker, $case->cond)
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$switch_vars = [$type_candidate_var => Type::parseString($fq_classlike_name)];
|
||||
|
||||
$case_context->vars_in_scope = array_merge(
|
||||
$case_context->vars_in_scope,
|
||||
$switch_vars
|
||||
);
|
||||
|
||||
$case_context->vars_possibly_in_scope = array_merge(
|
||||
$case_context->vars_possibly_in_scope,
|
||||
$switch_vars
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$switch_vars = $type_candidate_var && $case_type
|
||||
? [$type_candidate_var => Type::parseString($case_type)]
|
||||
: [];
|
||||
|
||||
$case_context = clone $original_context;
|
||||
$case_context->parent_context = $context;
|
||||
|
||||
$case_context->vars_in_scope = array_merge($case_context->vars_in_scope, $switch_vars);
|
||||
$case_context->vars_possibly_in_scope = array_merge($case_context->vars_possibly_in_scope, $switch_vars);
|
||||
|
||||
$case_stmts = $case->stmts;
|
||||
|
||||
// has a return/throw at end
|
||||
|
@ -428,7 +428,11 @@ class CallChecker
|
||||
$var = $stmt->args[0]->value;
|
||||
|
||||
if ($var instanceof PhpParser\Node\Expr\Variable && is_string($var->name)) {
|
||||
$stmt->inferredType = new Type\Union([new Type\Atomic\T('$' . $var->name)]);
|
||||
$atomic_type = $stmt->name->parts === ['get_class']
|
||||
? new Type\Atomic\GetClassT('$' . $var->name)
|
||||
: new Type\Atomic\GetTypeT('$' . $var->name);
|
||||
|
||||
$stmt->inferredType = new Type\Union([$atomic_type]);
|
||||
}
|
||||
}
|
||||
|
||||
|
6
src/Psalm/Type/Atomic/GetClassT.php
Normal file
6
src/Psalm/Type/Atomic/GetClassT.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class GetClassT extends T
|
||||
{
|
||||
}
|
6
src/Psalm/Type/Atomic/GetTypeT.php
Normal file
6
src/Psalm/Type/Atomic/GetTypeT.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class GetTypeT extends T
|
||||
{
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class T extends TString
|
||||
abstract class T extends TString
|
||||
{
|
||||
/**
|
||||
* Used to hold information as to what this refers to
|
||||
|
@ -95,7 +95,7 @@ class SwitchTypeTest extends TestCase
|
||||
testString($a);
|
||||
break;
|
||||
|
||||
case "int":
|
||||
case "integer":
|
||||
testInt($a);
|
||||
break;
|
||||
}',
|
||||
@ -134,6 +134,29 @@ class SwitchTypeTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'UndefinedMethod',
|
||||
],
|
||||
'getClassMissingClass' => [
|
||||
'<?php
|
||||
class A {}
|
||||
class B {}
|
||||
|
||||
$a = rand(0, 10) ? new A() : new B();
|
||||
|
||||
switch (get_class($a)) {
|
||||
case "C":
|
||||
break;
|
||||
}',
|
||||
'error_message' => 'UndefinedClass',
|
||||
],
|
||||
'getTypeNotAType' => [
|
||||
'<?php
|
||||
$a = rand(0, 10) ? 1 : "two";
|
||||
|
||||
switch (gettype($a)) {
|
||||
case "int":
|
||||
break;
|
||||
}',
|
||||
'error_message' => 'UnevaluatedCode',
|
||||
],
|
||||
'getTypeArgWrongArgs' => [
|
||||
'<?php
|
||||
function testInt(int $var) : void {
|
||||
@ -150,7 +173,7 @@ class SwitchTypeTest extends TestCase
|
||||
case "string":
|
||||
testInt($a);
|
||||
|
||||
case "int":
|
||||
case "integer":
|
||||
testString($a);
|
||||
}',
|
||||
'error_message' => 'InvalidScalarArgument',
|
||||
|
Loading…
x
Reference in New Issue
Block a user