1
0
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:
Matt Brown 2017-09-11 11:52:34 -04:00
parent 8a28ab4b26
commit d1807cfb95
7 changed files with 103 additions and 17 deletions

View File

@ -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
*/

View File

@ -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

View File

@ -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]);
}
}

View File

@ -0,0 +1,6 @@
<?php
namespace Psalm\Type\Atomic;
class GetClassT extends T
{
}

View File

@ -0,0 +1,6 @@
<?php
namespace Psalm\Type\Atomic;
class GetTypeT extends T
{
}

View File

@ -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

View File

@ -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',