1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-02 09:37:59 +01:00

Allow static property gets/sets

This commit is contained in:
Matthew Brown 2016-10-13 18:27:23 -04:00
parent 8da29add82
commit cd69b0e5d6
4 changed files with 366 additions and 166 deletions

View File

@ -215,7 +215,7 @@ abstract class ClassLikeChecker implements StatementsSource
if (!$class_context) {
$class_context = new Context($this->file_name, $this->absolute_class);
$class_context->parent = $this->parent_class;
$class_context->vars_in_scope['this'] = new Type\Union([new Type\Atomic($this->absolute_class)]);
$class_context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic($this->absolute_class)]);
}
// set all constants first
@ -435,7 +435,17 @@ abstract class ClassLikeChecker implements StatementsSource
);
foreach ($all_instance_properties as $property_name => $property_type) {
$class_context->vars_in_scope['this->' . $property_name] = $property_type;
$class_context->vars_in_scope['$this->' . $property_name] = $property_type;
}
$all_static_properties = array_merge(
self::$public_static_class_properties[$this->absolute_class],
self::$protected_static_class_properties[$this->absolute_class],
self::$private_static_class_properties[$this->absolute_class]
);
foreach ($all_static_properties as $property_name => $property_type) {
$class_context->vars_in_scope[$this->absolute_class . '::$' . $property_name] = $property_type;
}
$config = Config::getInstance();

View File

@ -73,7 +73,7 @@ abstract class FunctionLikeChecker implements StatementsSource
}
}
elseif ($context->self) {
$context->vars_in_scope['this'] = new Type\Union([new Type\Atomic($context->self)]);
$context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic($context->self)]);
}
$function_params = MethodChecker::getMethodParams($this->getMethodId());
@ -150,7 +150,7 @@ abstract class FunctionLikeChecker implements StatementsSource
}
}
$context->vars_in_scope[$function_param->name] = $param_type;
$context->vars_in_scope['$' . $function_param->name] = $param_type;
$statements_checker->registerVariable($function_param->name, $this->function->getLine());
}
@ -166,13 +166,13 @@ abstract class FunctionLikeChecker implements StatementsSource
}
foreach ($context->vars_in_scope as $var => $type) {
if (strpos($var, 'this->') !== 0) {
if (strpos($var, '$this->') !== 0) {
unset($context->vars_in_scope[$var]);
}
}
foreach ($context->vars_possibly_in_scope as $var => $type) {
if (strpos($var, 'this->') !== 0) {
if (strpos($var, '$this->') !== 0) {
unset($context->vars_possibly_in_scope[$var]);
}
}

View File

@ -200,8 +200,8 @@ class StatementsChecker
}
/*
if (isset($context->vars_in_scope['source_video->id'])) {
var_dump($stmt->getLine() . ' ' . $context->vars_in_scope['source_video->id']);
if (isset($context->vars_in_scope['Vimeo\Test\Framework\API\VimeoAPIUnitTest::$_factory'])) {
var_dump($stmt->getLine() . ' ' . $context->vars_in_scope['Vimeo\Test\Framework\API\VimeoAPIUnitTest::$_factory']);
}
*/
@ -232,7 +232,7 @@ class StatementsChecker
} elseif ($stmt instanceof PhpParser\Node\Stmt\Unset_) {
foreach ($stmt->vars as $var) {
$var_id = self::getArrayVarId($var);
$var_id = self::getArrayVarId($var, $this->absolute_class, $this->namespace, $this->aliased_classes);
if ($var_id) {
$context->remove($var_id);
@ -283,8 +283,8 @@ class StatementsChecker
foreach ($stmt->vars as $var) {
if ($var instanceof PhpParser\Node\Expr\Variable) {
if (is_string($var->name)) {
$context->vars_in_scope[$var->name] = Type::getMixed();
$context->vars_possibly_in_scope[$var->name] = true;
$context->vars_in_scope['$' . $var->name] = Type::getMixed();
$context->vars_possibly_in_scope['$' . $var->name] = true;
} else {
$this->checkExpression($var, $context);
}
@ -395,11 +395,26 @@ class StatementsChecker
$negatable_if_types = null;
if ($stmt->cond instanceof PhpParser\Node\Expr\BinaryOp) {
$reconcilable_if_types = $this->type_checker->getReconcilableTypeAssertions($stmt->cond);
$negatable_if_types = $this->type_checker->getNegatableTypeAssertions($stmt->cond);
$reconcilable_if_types = $this->type_checker->getReconcilableTypeAssertions(
$stmt->cond,
$this->absolute_class,
$this->namespace,
$this->aliased_classes
);
$negatable_if_types = $this->type_checker->getNegatableTypeAssertions(
$stmt->cond,
$this->absolute_class,
$this->namespace,
$this->aliased_classes
);
}
else {
$reconcilable_if_types = $negatable_if_types = $this->type_checker->getTypeAssertions($stmt->cond);
$reconcilable_if_types = $negatable_if_types = $this->type_checker->getTypeAssertions(
$stmt->cond,
$this->absolute_class,
$this->namespace,
$this->aliased_classes);
}
$has_ending_statements = ScopeChecker::doesAlwaysReturnOrThrow($stmt->stmts);
@ -555,11 +570,25 @@ class StatementsChecker
}
if ($elseif->cond instanceof PhpParser\Node\Expr\BinaryOp) {
$reconcilable_elseif_types = $this->type_checker->getReconcilableTypeAssertions($elseif->cond);
$negatable_elseif_types = $this->type_checker->getNegatableTypeAssertions($elseif->cond);
$reconcilable_elseif_types = $this->type_checker->getReconcilableTypeAssertions(
$elseif->cond,
$this->absolute_class,
$this->namespace,
$this->aliased_classes
);
$negatable_elseif_types = $this->type_checker->getNegatableTypeAssertions(
$elseif->cond,
$this->absolute_class,
$this->namespace,
$this->aliased_classes
);
}
else {
$reconcilable_elseif_types = $negatable_elseif_types = $this->type_checker->getTypeAssertions($elseif->cond);
$reconcilable_elseif_types = $negatable_elseif_types = $this->type_checker->getTypeAssertions(
$elseif->cond,
$this->absolute_class,
$this->namespace,
$this->aliased_classes);
}
$negated_elseif_types = $negatable_elseif_types
@ -873,11 +902,11 @@ class StatementsChecker
}
if ($this->check_variables) {
$context->vars_in_scope[$var->name] = $var->default && isset($var->default->inferredType)
$context->vars_in_scope['$' . $var->name] = $var->default && isset($var->default->inferredType)
? $var->default->inferredType
: Type::getMixed();
$context->vars_possibly_in_scope[$var->name] = true;
$context->vars_possibly_in_scope['$' . $var->name] = true;
$this->registerVariable($var->name, $var->getLine());
}
}
@ -956,7 +985,7 @@ class StatementsChecker
$isset_var->var->name === 'this' &&
is_string($isset_var->name)
) {
$var_id = 'this->' . $isset_var->name;
$var_id = '$this->' . $isset_var->name;
$context->vars_in_scope[$var_id] = Type::getMixed();
$context->vars_possibly_in_scope[$var_id] = true;
}
@ -1058,25 +1087,25 @@ class StatementsChecker
: $context->self;
if ($this_class) {
$use_context->vars_in_scope['this'] = new Type\Union([new Type\Atomic($this_class)]);
$use_context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic($this_class)]);
}
}
foreach ($context->vars_in_scope as $var => $type) {
if (strpos($var, 'this->') === 0) {
if (strpos($var, '$this->') === 0) {
$use_context->vars_in_scope[$var] = clone $type;
}
}
foreach ($context->vars_possibly_in_scope as $var => $type) {
if (strpos($var, 'this->') === 0) {
if (strpos($var, '$this->') === 0) {
$use_context->vars_possibly_in_scope[$var] = true;
}
}
foreach ($stmt->uses as $use) {
$use_context->vars_in_scope[$use->var] = isset($context->vars_in_scope[$use->var]) ? clone $context->vars_in_scope[$use->var] : Type::getMixed();
$use_context->vars_possibly_in_scope[$use->var] = true;
$use_context->vars_in_scope['$' . $use->var] = isset($context->vars_in_scope['$' . $use->var]) ? clone $context->vars_in_scope['$' . $use->var] : Type::getMixed();
$use_context->vars_possibly_in_scope['$' . $use->var] = true;
}
$closure_checker->check($use_context, $this->check_methods);
@ -1163,8 +1192,8 @@ class StatementsChecker
} elseif ($stmt instanceof PhpParser\Node\Expr\AssignRef) {
if ($stmt->var instanceof PhpParser\Node\Expr\Variable) {
if (is_string($stmt->var->name)) {
$context->vars_in_scope[$stmt->var->name] = Type::getMixed();
$context->vars_possibly_in_scope[$stmt->var->name] = true;
$context->vars_in_scope['$' . $stmt->var->name] = Type::getMixed();
$context->vars_possibly_in_scope['$' . $stmt->var->name] = true;
$this->registerVariable($stmt->var->name, $stmt->var->getLine());
}
else {
@ -1227,9 +1256,9 @@ class StatementsChecker
if (!$this->check_variables) {
$stmt->inferredType = Type::getMixed();
if (is_string($stmt->name) && !isset($context->vars_in_scope[$stmt->name])) {
$context->vars_in_scope[$stmt->name] = Type::getMixed();
$context->vars_possibly_in_scope[$stmt->name] = true;
if (is_string($stmt->name) && !isset($context->vars_in_scope['$' . $stmt->name])) {
$context->vars_in_scope['$' . $stmt->name] = Type::getMixed();
$context->vars_possibly_in_scope['$' . $stmt->name] = true;
}
return;
@ -1252,7 +1281,7 @@ class StatementsChecker
return;
}
$var_name = $stmt->name;
$var_name = '$' . $stmt->name;
if (!isset($context->vars_in_scope[$var_name])) {
if (!isset($context->vars_possibly_in_scope[$var_name]) || !isset($this->all_vars[$var_name])) {
@ -1266,7 +1295,7 @@ class StatementsChecker
}
else {
IssueBuffer::add(
new UndefinedVariable('Cannot find referenced variable $' . $var_name, $this->checked_file_name, $stmt->getLine())
new UndefinedVariable('Cannot find referenced variable ' . $var_name, $this->checked_file_name, $stmt->getLine())
);
return false;
@ -1278,7 +1307,7 @@ class StatementsChecker
if (IssueBuffer::accepts(
new PossiblyUndefinedVariable(
'Possibly undefined variable $' . $var_name .', first seen on line ' . $this->all_vars[$var_name],
'Possibly undefined variable ' . $var_name .', first seen on line ' . $this->all_vars[$var_name],
$this->checked_file_name,
$stmt->getLine()
),
@ -1351,7 +1380,7 @@ class StatementsChecker
*/
protected function assignByRefParam(PhpParser\Node\Expr $stmt, Type\Union $by_ref_type, Context $context)
{
$var_id = self::getVarId($stmt);
$var_id = self::getVarId($stmt, $this->absolute_class, $this->namespace, $this->aliased_classes);
if ($var_id && !isset($context->vars_in_scope[$var_id])) {
$context->vars_possibly_in_scope[$var_id] = true;
@ -1398,8 +1427,8 @@ class StatementsChecker
}
}
$stmt_var_id = self::getVarId($stmt->var);
$var_id = self::getVarId($stmt);
$stmt_var_id = self::getVarId($stmt->var, $this->absolute_class, $this->namespace, $this->aliased_classes);
$var_id = self::getVarId($stmt, $this->absolute_class, $this->namespace, $this->aliased_classes);
$var_name = is_string($stmt->name) ? $stmt->name : null;
@ -1426,7 +1455,7 @@ class StatementsChecker
if ($stmt_var_type->isNull()) {
if (IssueBuffer::accepts(
new NullReference(
'Cannot get property on null variable $' . $stmt_var_id,
'Cannot get property on null variable ' . $stmt_var_id,
$this->checked_file_name,
$stmt->getLine()
),
@ -1441,7 +1470,7 @@ class StatementsChecker
if ($stmt_var_type->isEmpty()) {
if (IssueBuffer::accepts(
new NullReference(
'Cannot fetch property on empty var $' . $stmt_var_id,
'Cannot fetch property on empty var ' . $stmt_var_id,
$this->checked_file_name,
$stmt->getLine()
),
@ -1456,7 +1485,7 @@ class StatementsChecker
if ($stmt_var_type->isMixed()) {
if (IssueBuffer::accepts(
new MixedPropertyFetch(
'Cannot fetch property on mixed var $' . $stmt_var_id,
'Cannot fetch property on mixed var ' . $stmt_var_id,
$this->checked_file_name,
$stmt->getLine()
),
@ -1471,7 +1500,7 @@ class StatementsChecker
if ($stmt_var_type->isNullable()) {
if (IssueBuffer::accepts(
new NullPropertyFetch(
'Cannot get property on possibly null variable $' . $stmt_var_id,
'Cannot get property on possibly null variable ' . $stmt_var_id,
$this->checked_file_name,
$stmt->getLine()
),
@ -1493,7 +1522,7 @@ class StatementsChecker
}
if (!$lhs_type_part->isObjectType()) {
$stmt_var_id = self::getVarId($stmt->var);
$stmt_var_id = self::getVarId($stmt->var, $this->absolute_class, $this->namespace, $this->aliased_classes);
if (IssueBuffer::accepts(
new InvalidPropertyFetch(
@ -1571,10 +1600,10 @@ class StatementsChecker
);
if (!$class_properties || !isset($class_properties[$stmt->name])) {
if ($stmt_var_id === 'this') {
if ($stmt_var_id === '$this') {
if (IssueBuffer::accepts(
new UndefinedThisPropertyFetch(
'Property ' . $lhs_type_part->value .'::$' . $stmt->name . ' is not defined',
'Instance property ' . $lhs_type_part->value .'::$' . $stmt->name . ' is not defined',
$this->checked_file_name,
$stmt->getLine()
),
@ -1586,7 +1615,7 @@ class StatementsChecker
else {
if (IssueBuffer::accepts(
new UndefinedPropertyFetch(
'Property ' . $lhs_type_part->value .'::$' . $stmt->name . ' is not defined',
'Instance property ' . $lhs_type_part->value .'::$' . $stmt->name . ' is not defined',
$this->checked_file_name,
$stmt->getLine()
),
@ -1637,7 +1666,7 @@ class StatementsChecker
$var_id = 'this->' . $prop_name;
}
elseif ($stmt->var instanceof PhpParser\Node\Expr\Variable) {
if (!isset($context->vars_in_scope[$stmt->var->name])) {
if (!isset($context->vars_in_scope['$' . $stmt->var->name])) {
if ($this->checkVariable($stmt->var, $context) === false) {
return false;
}
@ -1645,9 +1674,9 @@ class StatementsChecker
return;
}
$stmt->var->inferredType = $context->vars_in_scope[$stmt->var->name];
$stmt->var->inferredType = $context->vars_in_scope['$' . $stmt->var->name];
$lhs_type = $context->vars_in_scope[$stmt->var->name];
$lhs_type = $context->vars_in_scope['$' . $stmt->var->name];
if ($stmt->var->name === 'this' && !$this->source->getClassLikeChecker()) {
if (IssueBuffer::accepts(
@ -1658,12 +1687,12 @@ class StatementsChecker
}
}
$var_id = self::getVarId($stmt);
$var_id = self::getVarId($stmt, $this->absolute_class, $this->namespace, $this->aliased_classes);
if ($lhs_type->isMixed()) {
if (IssueBuffer::accepts(
new MixedPropertyAssignment(
'$' . $var_id . ' with mixed type cannot be assigned to',
$var_id . ' with mixed type cannot be assigned to',
$this->checked_file_name,
$stmt->getLine()
),
@ -1678,7 +1707,7 @@ class StatementsChecker
if ($lhs_type->isNull()) {
if (IssueBuffer::accepts(
new NullPropertyAssignment(
'$' . $var_id . ' with null type cannot be assigned to',
$var_id . ' with null type cannot be assigned to',
$this->checked_file_name,
$stmt->getLine()
),
@ -1693,7 +1722,7 @@ class StatementsChecker
if ($lhs_type->isNullable()) {
if (IssueBuffer::accepts(
new NullPropertyAssignment(
'$' . $var_id . ' with possibly null type \'' . $lhs_type . '\' cannot be assigned to',
$var_id . ' with possibly null type \'' . $lhs_type . '\' cannot be assigned to',
$this->checked_file_name,
$stmt->getLine()
),
@ -1720,7 +1749,7 @@ class StatementsChecker
if (!$lhs_type_part->isObjectType()) {
if (IssueBuffer::accepts(
new InvalidPropertyAssignment(
'$' . $var_id . ' with possible non-object type \'' . $lhs_type_part . '\' cannot be assigned to',
$var_id . ' with possible non-object type \'' . $lhs_type_part . '\' cannot be assigned to',
$this->checked_file_name,
$stmt->getLine()
),
@ -1845,7 +1874,7 @@ class StatementsChecker
if (!$class_property_types) {
if (IssueBuffer::accepts(
new MissingPropertyDeclaration(
'Missing property declaration for $' . $var_id,
'Missing property declaration for ' . $var_id,
$this->checked_file_name,
$stmt->getLine()
),
@ -1869,7 +1898,7 @@ class StatementsChecker
if (!$assignment_type->isIn($class_property_type)) {
if (IssueBuffer::accepts(
new InvalidPropertyAssignment(
'$' . $var_id . ' with declared type \'' . $class_property_type . '\' cannot be assigned type \'' . $assignment_type . '\'',
$var_id . ' with declared type \'' . $class_property_type . '\' cannot be assigned type \'' . $assignment_type . '\'',
$this->checked_file_name,
$stmt->getLine()
),
@ -1881,6 +1910,99 @@ class StatementsChecker
}
}
/**
* @param PhpParser\Node\Expr\StaticPropertyFetch $stmt
* @param string $prop_name
* @param Type\Union $assignment_type
* @param Context $context
* @return false|null
*/
protected function checkStaticPropertyAssignment(PhpParser\Node\Expr\StaticPropertyFetch $stmt, Type\Union $assignment_type, Context $context)
{
$class_property_types = [];
if ($this->checkStaticPropertyFetch($stmt, $context) === false) {
return false;
}
$var_id = self::getVarId($stmt, $this->absolute_class, $this->namespace, $this->aliased_classes);
$absolute_class = (string)$stmt->class->inferredType;
if ($stmt->class->parts[0] === 'this' || $absolute_class === $context->self) {
$class_visibility = \ReflectionProperty::IS_PRIVATE;
}
elseif (ClassChecker::classExtends($absolute_class, $context->self)) {
$class_visibility = \ReflectionProperty::IS_PROTECTED;
}
else {
$class_visibility = \ReflectionProperty::IS_PUBLIC;
}
$class_properties = ClassLikeChecker::getStaticPropertiesForClass(
$absolute_class,
$class_visibility
);
$prop_name = $stmt->name;
if (!isset($class_properties[$prop_name])) {
if ($stmt->class->parts[0] === 'this') {
if (IssueBuffer::accepts(
new UndefinedThisPropertyAssignment(
'Static property ' . $var_id . ' is not defined',
$this->checked_file_name,
$stmt->getLine()
),
$this->suppressed_issues
)) {
return false;
}
}
else {
if (IssueBuffer::accepts(
new UndefinedPropertyAssignment(
'Static property ' . $var_id . ' is not defined',
$this->checked_file_name,
$stmt->getLine()
),
$this->suppressed_issues
)) {
return false;
}
}
continue;
}
$context->vars_in_scope[$var_id] = $assignment_type;
$class_property_type = clone $class_properties[$prop_name];
if ($assignment_type->isMixed()) {
return;
}
if ($class_property_type->isMixed()) {
return;
}
if (!$assignment_type->isIn($class_property_type)) {
if (IssueBuffer::accepts(
new InvalidPropertyAssignment(
$var_id . ' with declared type \'' . $class_property_type . '\' cannot be assigned type \'' . $assignment_type . '\'',
$this->checked_file_name,
$stmt->getLine()
),
$this->suppressed_issues
)) {
return false;
}
}
$context->vars_in_scope[$var_id] = $assignment_type;
}
/**
* @return false|null
*/
@ -2033,11 +2155,11 @@ class StatementsChecker
}
}
$catch_context->vars_in_scope[$catch->var] = new Type\Union([
$catch_context->vars_in_scope['$' . $catch->var] = new Type\Union([
new Type\Atomic($catch_class)
]);
$catch_context->vars_possibly_in_scope[$catch->var] = true;
$catch_context->vars_possibly_in_scope['$' . $catch->var] = true;
$this->registerVariable($catch->var, $catch->getLine());
@ -2122,7 +2244,7 @@ class StatementsChecker
/** @var Type\Union|null */
$value_type = null;
$var_id = self::getVarId($stmt->expr);
$var_id = self::getVarId($stmt->expr, $this->absolute_class, $this->namespace, $this->aliased_classes);
if (isset($stmt->expr->inferredType)) {
/** @var Type\Union */
@ -2233,8 +2355,8 @@ class StatementsChecker
}
if ($stmt->keyVar) {
$foreach_context->vars_in_scope[$stmt->keyVar->name] = $key_type ?: Type::getMixed();
$foreach_context->vars_possibly_in_scope[$stmt->keyVar->name] = true;
$foreach_context->vars_in_scope['$' . $stmt->keyVar->name] = $key_type ?: Type::getMixed();
$foreach_context->vars_possibly_in_scope['$' . $stmt->keyVar->name] = true;
$this->registerVariable($stmt->keyVar->name, $stmt->getLine());
}
@ -2242,8 +2364,8 @@ class StatementsChecker
$value_type = new Type\Union([$value_type]);
}
$foreach_context->vars_in_scope[$stmt->valueVar->name] = $value_type ? $value_type : Type::getMixed();
$foreach_context->vars_possibly_in_scope[$stmt->valueVar->name] = true;
$foreach_context->vars_in_scope['$' . $stmt->valueVar->name] = $value_type ? $value_type : Type::getMixed();
$foreach_context->vars_possibly_in_scope['$' . $stmt->valueVar->name] = true;
$this->registerVariable($stmt->valueVar->name, $stmt->getLine());
CommentChecker::getTypeFromComment((string) $stmt->getDocComment(), $foreach_context, $this->source, null);
@ -2283,7 +2405,11 @@ class StatementsChecker
return false;
}
$while_types = $this->type_checker->getTypeAssertions($stmt->cond);
$while_types = $this->type_checker->getTypeAssertions(
$stmt->cond,
$this->absolute_class,
$this->namespace,
$this->aliased_classes);
// if the while has an or as the main component, we cannot safely reason about it
if ($stmt->cond instanceof PhpParser\Node\Expr\BinaryOp && self::containsBooleanOr($stmt->cond)) {
@ -2350,7 +2476,12 @@ class StatementsChecker
// ignore deeply-nested string concatenation
}
else if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd) {
$left_type_assertions = $this->type_checker->getReconcilableTypeAssertions($stmt->left);
$left_type_assertions = $this->type_checker->getReconcilableTypeAssertions(
$stmt->left,
$this->absolute_class,
$this->namespace,
$this->aliased_classes
);
if ($this->checkExpression($stmt->left, $context) === false) {
return false;
@ -2387,7 +2518,12 @@ class StatementsChecker
$context->vars_possibly_in_scope = array_merge($op_context->vars_possibly_in_scope, $context->vars_possibly_in_scope);
}
else if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) {
$left_type_assertions = $this->type_checker->getNegatableTypeAssertions($stmt->left);
$left_type_assertions = $this->type_checker->getNegatableTypeAssertions(
$stmt->left,
$this->absolute_class,
$this->namespace,
$this->aliased_classes
);
$negated_type_assertions = TypeChecker::negateTypes($left_type_assertions);
@ -2466,9 +2602,9 @@ class StatementsChecker
*/
protected function checkAssignment(PhpParser\Node\Expr\Assign $stmt, Context $context)
{
$var_id = self::getVarId($stmt->var);
$var_id = self::getVarId($stmt->var, $this->absolute_class, $this->namespace, $this->aliased_classes);
$array_var_id = self::getArrayVarId($stmt->var);
$array_var_id = self::getArrayVarId($stmt->var, $this->absolute_class, $this->namespace, $this->aliased_classes);
if ($array_var_id) {
// removes dependennt vars from $context
@ -2503,21 +2639,24 @@ class StatementsChecker
$context->vars_possibly_in_scope[$var_id] = true;
$this->registerVariable($var_id, $stmt->var->getLine());
} elseif ($stmt->var instanceof PhpParser\Node\Expr\List_) {
}
elseif ($stmt->var instanceof PhpParser\Node\Expr\List_) {
foreach ($stmt->var->vars as $var) {
if ($var) {
$context->vars_in_scope[$var->name] = Type::getMixed();
$context->vars_possibly_in_scope[$var->name] = true;
$context->vars_in_scope['$' . $var->name] = Type::getMixed();
$context->vars_possibly_in_scope['$' . $var->name] = true;
$this->registerVariable($var->name, $var->getLine());
}
}
} else if ($stmt->var instanceof PhpParser\Node\Expr\ArrayDimFetch) {
}
else if ($stmt->var instanceof PhpParser\Node\Expr\ArrayDimFetch) {
if ($this->checkArrayAssignment($stmt->var, $context, $return_type) === false) {
return false;
}
} else if ($stmt->var instanceof PhpParser\Node\Expr\PropertyFetch &&
}
else if ($stmt->var instanceof PhpParser\Node\Expr\PropertyFetch &&
$stmt->var->var instanceof PhpParser\Node\Expr\Variable &&
is_string($stmt->var->name)) {
@ -2525,10 +2664,18 @@ class StatementsChecker
$context->vars_possibly_in_scope[$var_id] = true;
}
else if ($stmt->var instanceof PhpParser\Node\Expr\StaticPropertyFetch &&
$stmt->var->class instanceof PhpParser\Node\Name &&
is_string($stmt->var->name)) {
$this->checkStaticPropertyAssignment($stmt->var, $return_type, $context);
$context->vars_possibly_in_scope[$var_id] = true;
}
if ($var_id && isset($context->vars_in_scope[$var_id]) && $context->vars_in_scope[$var_id]->isVoid()) {
if (IssueBuffer::accepts(
new FailedTypeResolution('Cannot assign $' . $var_id . ' to type void', $this->checked_file_name, $stmt->getLine()),
new FailedTypeResolution('Cannot assign ' . $var_id . ' to type void', $this->checked_file_name, $stmt->getLine()),
$this->suppressed_issues
)) {
return false;
@ -2539,14 +2686,29 @@ class StatementsChecker
/**
* @return string|null
*/
public static function getVarId(PhpParser\Node\Expr $stmt, &$nesting = null)
public static function getVarId(PhpParser\Node\Expr $stmt, $this_class_name, $namespace = null, array $aliased_classes = null, &$nesting = null)
{
if ($stmt instanceof PhpParser\Node\Expr\Variable && is_string($stmt->name)) {
return $stmt->name;
return '$' . $stmt->name;
}
if ($stmt instanceof PhpParser\Node\Expr\StaticPropertyFetch
&& is_string($stmt->name)
&& $stmt->class instanceof PhpParser\Node\Name
) {
if (count($stmt->class->parts) === 1 && in_array($stmt->class->parts[0], ['self', 'static', 'parent'])) {
$absolute_class = $this_class_name;
}
else {
$absolute_class = ClassLikeChecker::getAbsoluteClassFromName($stmt->class, $namespace, $aliased_classes);
}
return $absolute_class . '::$' . $stmt->name;
}
if ($stmt instanceof PhpParser\Node\Expr\PropertyFetch && is_string($stmt->name)) {
$object_id = self::getVarId($stmt->var);
$object_id = self::getVarId($stmt->var, $this_class_name, $namespace, $aliased_classes);
if (!$object_id) {
return null;
@ -2557,7 +2719,7 @@ class StatementsChecker
if ($stmt instanceof PhpParser\Node\Expr\ArrayDimFetch && $nesting !== null) {
$nesting++;
return self::getVarId($stmt->var, $nesting);
return self::getVarId($stmt->var, $this_class_name, $namespace, $aliased_classes, $nesting);
}
return null;
@ -2566,14 +2728,14 @@ class StatementsChecker
/**
* @return string|null
*/
public static function getArrayVarId(PhpParser\Node\Expr $stmt)
public static function getArrayVarId(PhpParser\Node\Expr $stmt, $this_class_name, $namespace, array $aliased_classes)
{
if ($stmt instanceof PhpParser\Node\Expr\ArrayDimFetch && $stmt->dim instanceof PhpParser\Node\Scalar\String_) {
$root_var_id = self::getArrayVarId($stmt->var);
$root_var_id = self::getArrayVarId($stmt->var, $this_class_name, $namespace, $aliased_classes);
return $root_var_id ? $root_var_id . '[\'' . $stmt->dim->value . '\']' : null;
}
return self::getVarId($stmt);
return self::getVarId($stmt, $this_class_name, $namespace, $aliased_classes);
}
/**
@ -2607,7 +2769,7 @@ class StatementsChecker
}
$nesting = 0;
$var_id = self::getVarId($stmt->var, $nesting);
$var_id = self::getVarId($stmt->var, $this->absolute_class, $this->namespace, $this->aliased_classes, $nesting);
$is_object = $var_id && isset($context->vars_in_scope[$var_id]) && $context->vars_in_scope[$var_id]->hasObjectType();
$is_string = $var_id && isset($context->vars_in_scope[$var_id]) && $context->vars_in_scope[$var_id]->hasString();
@ -2615,7 +2777,7 @@ class StatementsChecker
return false;
}
$array_var_id = self::getArrayVarId($stmt->var);
$array_var_id = self::getArrayVarId($stmt->var, $this->absolute_class, $this->namespace, $this->aliased_classes);
$keyed_array_var_id = $array_var_id && $stmt->dim instanceof PhpParser\Node\Scalar\String_
? $array_var_id . '[\'' . $stmt->dim->value . '\']'
: null;
@ -2632,7 +2794,7 @@ class StatementsChecker
if ($value_type->isMixed()) {
if (IssueBuffer::accepts(
new MixedStringOffsetAssignment(
'Cannot assign a mixed variable to a string offset for $' . $var_id,
'Cannot assign a mixed variable to a string offset for ' . $var_id,
$this->checked_file_name,
$stmt->getLine()
),
@ -2646,7 +2808,7 @@ class StatementsChecker
if (IssueBuffer::accepts(
new InvalidArrayAssignment(
'Cannot assign string offset for $' . $var_id . ' of type ' . $value_type,
'Cannot assign string offset for ' . $var_id . ' of type ' . $value_type,
$this->checked_file_name,
$stmt->getLine()
),
@ -2763,7 +2925,7 @@ class StatementsChecker
if (!$type->isArray() && !$type->isObjectLike() && !ClassChecker::classImplements($type->value, 'ArrayAccess')) {
if (IssueBuffer::accepts(
new InvalidArrayAssignment(
'Cannot assign value on variable $' . $var_id . ' of type ' . $type->value . ' that does not implement ArrayAccess',
'Cannot assign value on variable ' . $var_id . ' of type ' . $type->value . ' that does not implement ArrayAccess',
$this->checked_file_name,
$line_number
),
@ -2831,7 +2993,7 @@ class StatementsChecker
}
}
$var_id = self::getVarId($stmt->var);
$var_id = self::getVarId($stmt->var, $this->absolute_class, $this->namespace, $this->aliased_classes);
$class_type = isset($context->vars_in_scope[$var_id]) ? $context->vars_in_scope[$var_id] : null;
@ -2885,7 +3047,7 @@ class StatementsChecker
case 'null':
if (IssueBuffer::accepts(
new NullReference(
'Cannot call method ' . $stmt->name . ' on possibly null variable ' . $class_type,
'Cannot call method ' . $stmt->name . ' on possibly null variable ' . $var_id,
$this->checked_file_name,
$stmt->getLine()
),
@ -2902,7 +3064,7 @@ class StatementsChecker
case 'string':
if (IssueBuffer::accepts(
new InvalidArgument(
'Cannot call method ' . $stmt->name . ' on ' . $class_type . ' variable',
'Cannot call method ' . $stmt->name . ' on ' . $class_type . ' variable ' . $var_id,
$this->checked_file_name,
$stmt->getLine()
),
@ -2916,7 +3078,7 @@ class StatementsChecker
case 'object':
if (IssueBuffer::accepts(
new MixedMethodCall(
'Cannot call method ' . $stmt->name . ' on a mixed variable',
'Cannot call method ' . $stmt->name . ' on a mixed variable ' . $var_id,
$this->checked_file_name,
$stmt->getLine()
),
@ -3051,15 +3213,15 @@ class StatementsChecker
protected function checkClosureUses(PhpParser\Node\Expr\Closure $stmt, Context $context)
{
foreach ($stmt->uses as $use) {
if (!isset($context->vars_in_scope[$use->var])) {
if (!isset($context->vars_in_scope['$' . $use->var])) {
if ($use->byRef) {
$context->vars_in_scope[$use->var] = Type::getMixed();
$context->vars_possibly_in_scope[$use->var] = true;
$context->vars_in_scope['$' . $use->var] = Type::getMixed();
$context->vars_possibly_in_scope['$' . $use->var] = true;
$this->registerVariable($use->var, $use->getLine());
return;
}
if (!isset($context->vars_possibly_in_scope[$use->var])) {
if (!isset($context->vars_possibly_in_scope['$' . $use->var])) {
if ($this->check_variables) {
IssueBuffer::add(
new UndefinedVariable('Cannot find referenced variable $' . $use->var, $this->checked_file_name, $use->getLine())
@ -3384,7 +3546,7 @@ class StatementsChecker
}
}
else {
$var_id = self::getVarId($arg->value);
$var_id = self::getVarId($arg->value, $this->absolute_class, $this->namespace, $this->aliased_classes);
if ($var_id && (!isset($context->vars_in_scope[$var_id]) || $context->vars_in_scope[$var_id]->isNull())) {
// we don't know if it exists, assume it's passed by reference
@ -3409,10 +3571,10 @@ class StatementsChecker
}
} elseif (is_string($arg->value->name)) {
if (false || !isset($context->vars_in_scope[$arg->value->name]) || $context->vars_in_scope[$arg->value->name]->isNull()) {
if (false || !isset($context->vars_in_scope['$' . $arg->value->name]) || $context->vars_in_scope['$' . $arg->value->name]->isNull()) {
// we don't know if it exists, assume it's passed by reference
$context->vars_in_scope[$arg->value->name] = Type::getMixed();
$context->vars_possibly_in_scope[$arg->value->name] = true;
$context->vars_in_scope['$' . $arg->value->name] = Type::getMixed();
$context->vars_possibly_in_scope['$' . $arg->value->name] = true;
$this->registerVariable($arg->value->name, $arg->value->getLine());
}
}
@ -3592,9 +3754,19 @@ class StatementsChecker
return false;
}
}
$stmt->class->inferredType = $absolute_class ? new Type\Union([new Type\Atomic($absolute_class)]) : null;
}
if ($absolute_class && $this->check_variables && is_string($stmt->name) && !self::isMock($absolute_class)) {
$var_id = self::getVarId($stmt, $this->absolute_class, $this->namespace, $this->aliased_classes);
if ($var_id && isset($context->vars_in_scope[$var_id])) {
// we don't need to check anything
$stmt->inferredType = $context->vars_in_scope[$var_id];
return;
}
if ($absolute_class === $context->self
|| ($this->source->getSource() instanceof TraitChecker && $absolute_class === $this->source->getAbsoluteClass())
) {
@ -3612,8 +3784,6 @@ class StatementsChecker
$class_visibility
);
$var_id = $absolute_class . '::$' . $stmt->name;
if (!isset($visible_class_properties[$stmt->name])) {
$all_class_properties = [];
@ -3685,11 +3855,25 @@ class StatementsChecker
$t_if_context = clone $context;
if ($stmt->cond instanceof PhpParser\Node\Expr\BinaryOp) {
$reconcilable_if_types = $this->type_checker->getReconcilableTypeAssertions($stmt->cond);
$negatable_if_types = $this->type_checker->getNegatableTypeAssertions($stmt->cond);
$reconcilable_if_types = $this->type_checker->getReconcilableTypeAssertions(
$stmt->cond,
$this->absolute_class,
$this->namespace,
$this->aliased_classes
);
$negatable_if_types = $this->type_checker->getNegatableTypeAssertions(
$stmt->cond,
$this->absolute_class,
$this->namespace,
$this->aliased_classes
);
}
else {
$reconcilable_if_types = $negatable_if_types = $this->type_checker->getTypeAssertions($stmt->cond);
$reconcilable_if_types = $negatable_if_types = $this->type_checker->getTypeAssertions(
$stmt->cond,
$this->absolute_class,
$this->namespace,
$this->aliased_classes);
}
$if_return_type = null;
@ -4152,10 +4336,10 @@ class StatementsChecker
$key_value = null;
$nesting = 0;
$var_id = self::getVarId($stmt->var, $nesting);
$var_id = self::getVarId($stmt->var, $this->absolute_class, $this->namespace, $this->aliased_classes, $nesting);
$is_object = $var_id && isset($context->vars_in_scope[$var_id]) && $context->vars_in_scope[$var_id]->hasObjectType();
$array_var_id = self::getArrayVarId($stmt->var);
$array_var_id = self::getArrayVarId($stmt->var, $this->absolute_class, $this->namespace, $this->aliased_classes);
$keyed_array_var_id = $array_var_id && $stmt->dim instanceof PhpParser\Node\Scalar\String_
? $array_var_id . '[\'' . $stmt->dim->value . '\']'
: null;
@ -4211,7 +4395,7 @@ class StatementsChecker
if ($type->isScalarType() && !$type->isString()) {
if (IssueBuffer::accepts(
new InvalidArrayAssignment(
'Cannot assign value on variable $' . $var_id . ' of scalar type ' . $type->value,
'Cannot assign value on variable ' . $var_id . ' of scalar type ' . $type->value,
$this->checked_file_name,
$stmt->getLine()
),
@ -4401,7 +4585,7 @@ class StatementsChecker
if (IssueBuffer::accepts(
new InvalidArrayAccess(
'Cannot access value on object-like variable $' . $var_id . ' using int offset - expecting ' . $expected_keys_string,
'Cannot access value on object-like variable ' . $var_id . ' using int offset - expecting ' . $expected_keys_string,
$this->checked_file_name,
$stmt->getLine()
),
@ -4444,7 +4628,7 @@ class StatementsChecker
if (($at->isMixed() || $at->isEmpty()) && !$key_type->isMixed()) {
if (IssueBuffer::accepts(
new MixedArrayOffset(
'Cannot access value on variable $' . $var_id . ' using mixed offset - expecting ' . $key_type,
'Cannot access value on variable ' . $var_id . ' using mixed offset - expecting ' . $key_type,
$this->checked_file_name,
$stmt->getLine()
),
@ -4456,7 +4640,7 @@ class StatementsChecker
elseif (!$at->isIn($key_type)) {
if (IssueBuffer::accepts(
new InvalidArrayAccess(
'Cannot access value on variable $' . $var_id . ' using ' . $at . ' offset - expecting ' . $key_type,
'Cannot access value on variable ' . $var_id . ' using ' . $at . ' offset - expecting ' . $key_type,
$this->checked_file_name,
$stmt->getLine()
),

View File

@ -32,11 +32,11 @@ class TypeChecker
* @param PhpParser\Node\Expr $conditional [description]
* @return array<string,string>
*/
public function getReconcilableTypeAssertions(PhpParser\Node\Expr $conditional)
public function getReconcilableTypeAssertions(PhpParser\Node\Expr $conditional, $this_class_name, $namespace, array $aliased_classes)
{
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) {
$left_assertions = $this->getReconcilableTypeAssertions($conditional->left);
$right_assertions = $this->getReconcilableTypeAssertions($conditional->right);
$left_assertions = $this->getReconcilableTypeAssertions($conditional->left, $this_class_name, $namespace, $aliased_classes);
$right_assertions = $this->getReconcilableTypeAssertions($conditional->right, $this_class_name, $namespace, $aliased_classes);
$keys = array_intersect(array_keys($left_assertions), array_keys($right_assertions));
@ -52,33 +52,33 @@ class TypeChecker
}
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd) {
$left_assertions = $this->getReconcilableTypeAssertions($conditional->left);
$right_assertions = $this->getReconcilableTypeAssertions($conditional->right);
$left_assertions = $this->getReconcilableTypeAssertions($conditional->left, $this_class_name, $namespace, $aliased_classes);
$right_assertions = $this->getReconcilableTypeAssertions($conditional->right, $this_class_name, $namespace, $aliased_classes);
return self::combineTypeAssertions($left_assertions, $right_assertions);
}
return $this->getTypeAssertions($conditional);
return $this->getTypeAssertions($conditional, $this_class_name, $namespace, $aliased_classes);
}
/**
* @param PhpParser\Node\Expr $conditional
* @return array<string,string>
*/
public function getNegatableTypeAssertions(PhpParser\Node\Expr $conditional)
public function getNegatableTypeAssertions(PhpParser\Node\Expr $conditional, $this_class_name, $namespace, array $aliased_classes)
{
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd) {
return [];
}
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) {
$left_assertions = $this->getNegatableTypeAssertions($conditional->left);
$right_assertions = $this->getNegatableTypeAssertions($conditional->right);
$left_assertions = $this->getNegatableTypeAssertions($conditional->left, $this_class_name, $namespace, $aliased_classes);
$right_assertions = $this->getNegatableTypeAssertions($conditional->right, $this_class_name, $namespace, $aliased_classes);
return self::combineTypeAssertions($left_assertions, $right_assertions);
}
return $this->getTypeAssertions($conditional);
return $this->getTypeAssertions($conditional, $this_class_name, $namespace, $aliased_classes);
}
private static function combineTypeAssertions(array $left_assertions, array $right_assertions)
@ -114,7 +114,7 @@ class TypeChecker
* @param PhpParser\Node\Expr $conditional
* @return array<string,string>
*/
public function getTypeAssertions(PhpParser\Node\Expr $conditional)
public function getTypeAssertions(PhpParser\Node\Expr $conditional, $this_class_name, $namespace, array $aliased_classes)
{
$if_types = [];
@ -122,17 +122,17 @@ class TypeChecker
$instanceof_type = $this->getInstanceOfTypes($conditional);
if ($instanceof_type) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr);
$var_name = StatementsChecker::getArrayVarId($conditional->expr, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $instanceof_type;
}
}
}
else if ($var_name = StatementsChecker::getArrayVarId($conditional)) {
else if ($var_name = StatementsChecker::getArrayVarId($conditional, $this_class_name, $namespace, $aliased_classes)) {
$if_types[$var_name] = '!empty';
}
else if ($conditional instanceof PhpParser\Node\Expr\Assign) {
$var_name = StatementsChecker::getArrayVarId($conditional->var);
$var_name = StatementsChecker::getArrayVarId($conditional->var, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = '!empty';
}
@ -142,17 +142,17 @@ class TypeChecker
$instanceof_type = $this->getInstanceOfTypes($conditional->expr);
if ($instanceof_type) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr->expr);
$var_name = StatementsChecker::getArrayVarId($conditional->expr->expr, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = '!' . $instanceof_type;
}
}
}
else if ($var_name = StatementsChecker::getArrayVarId($conditional->expr)) {
else if ($var_name = StatementsChecker::getArrayVarId($conditional->expr, $this_class_name, $namespace, $aliased_classes)) {
$if_types[$var_name] = 'empty';
}
else if ($conditional->expr instanceof PhpParser\Node\Expr\Assign) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr->var);
$var_name = StatementsChecker::getArrayVarId($conditional->expr->var, $this_class_name, $namespace, $aliased_classes);
$if_types[$var_name] = 'empty';
}
else if ($conditional->expr instanceof PhpParser\Node\Expr\BinaryOp\Identical || $conditional->expr instanceof PhpParser\Node\Expr\BinaryOp\Equal) {
@ -161,10 +161,10 @@ class TypeChecker
if ($null_position !== null) {
if ($null_position === self::ASSIGNMENT_TO_RIGHT) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr->left);
$var_name = StatementsChecker::getArrayVarId($conditional->expr->left, $this_class_name, $namespace, $aliased_classes);
}
else if ($null_position === self::ASSIGNMENT_TO_LEFT) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr->right);
$var_name = StatementsChecker::getArrayVarId($conditional->expr->right, $this_class_name, $namespace, $aliased_classes);
}
else {
throw new \InvalidArgumentException('Bad null variable position');
@ -182,10 +182,10 @@ class TypeChecker
}
elseif ($false_position !== null) {
if ($false_position === self::ASSIGNMENT_TO_RIGHT) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr->left);
$var_name = StatementsChecker::getArrayVarId($conditional->expr->left, $this_class_name, $namespace, $aliased_classes);
}
else if ($false_position === self::ASSIGNMENT_TO_LEFT) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr->right);
$var_name = StatementsChecker::getArrayVarId($conditional->expr->right, $this_class_name, $namespace, $aliased_classes);
}
else {
throw new \InvalidArgumentException('Bad null variable position');
@ -208,10 +208,10 @@ class TypeChecker
if ($null_position !== null) {
if ($null_position === self::ASSIGNMENT_TO_RIGHT) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr->left);
$var_name = StatementsChecker::getArrayVarId($conditional->expr->left, $this_class_name, $namespace, $aliased_classes);
}
else if ($null_position === self::ASSIGNMENT_TO_LEFT) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr->right);
$var_name = StatementsChecker::getArrayVarId($conditional->expr->right, $this_class_name, $namespace, $aliased_classes);
}
else {
throw new \InvalidArgumentException('Bad null variable position');
@ -228,10 +228,10 @@ class TypeChecker
}
elseif ($false_position !== null) {
if ($false_position === self::ASSIGNMENT_TO_RIGHT) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr->left);
$var_name = StatementsChecker::getArrayVarId($conditional->expr->left, $this_class_name, $namespace, $aliased_classes);
}
else if ($false_position === self::ASSIGNMENT_TO_LEFT) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr->right);
$var_name = StatementsChecker::getArrayVarId($conditional->expr->right, $this_class_name, $namespace, $aliased_classes);
}
else {
throw new \InvalidArgumentException('Bad null variable position');
@ -249,18 +249,18 @@ class TypeChecker
}
}
else if ($conditional->expr instanceof PhpParser\Node\Expr\Empty_) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr->expr);
$var_name = StatementsChecker::getArrayVarId($conditional->expr->expr, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = '!empty';
}
}
elseif ($conditional->expr instanceof PhpParser\Node\Expr\FuncCall) {
self::processFunctionCall($conditional->expr, $if_types, true);
self::processFunctionCall($conditional->expr, $if_types, true, $this_class_name, $namespace, $aliased_classes);
}
else if ($conditional->expr instanceof PhpParser\Node\Expr\Isset_) {
foreach ($conditional->expr->vars as $isset_var) {
$var_name = StatementsChecker::getArrayVarId($isset_var);
$var_name = StatementsChecker::getArrayVarId($isset_var, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = 'null';
}
@ -276,10 +276,10 @@ class TypeChecker
if ($null_position !== null) {
if ($null_position === self::ASSIGNMENT_TO_RIGHT) {
$var_name = StatementsChecker::getArrayVarId($conditional->left);
$var_name = StatementsChecker::getArrayVarId($conditional->left, $this_class_name, $namespace, $aliased_classes);
}
else if ($null_position === self::ASSIGNMENT_TO_LEFT) {
$var_name = StatementsChecker::getArrayVarId($conditional->right);
$var_name = StatementsChecker::getArrayVarId($conditional->right, $this_class_name, $namespace, $aliased_classes);
}
else {
throw new \InvalidArgumentException('Bad null variable position');
@ -297,18 +297,18 @@ class TypeChecker
elseif ($false_position) {
if ($false_position === self::ASSIGNMENT_TO_RIGHT) {
if ($conditional->left instanceof PhpParser\Node\Expr\FuncCall) {
self::processFunctionCall($conditional->left, $if_types, true);
self::processFunctionCall($conditional->left, $if_types, true, $this_class_name, $namespace, $aliased_classes);
}
else {
$var_name = StatementsChecker::getArrayVarId($conditional->left);
$var_name = StatementsChecker::getArrayVarId($conditional->left, $this_class_name, $namespace, $aliased_classes);
}
}
else if ($false_position === self::ASSIGNMENT_TO_LEFT) {
if ($conditional->right instanceof PhpParser\Node\Expr\FuncCall) {
self::processFunctionCall($conditional->right, $if_types, true);
self::processFunctionCall($conditional->right, $if_types, true, $this_class_name, $namespace, $aliased_classes);
}
else {
$var_name = StatementsChecker::getArrayVarId($conditional->right);
$var_name = StatementsChecker::getArrayVarId($conditional->right, $this_class_name, $namespace, $aliased_classes);
}
}
else {
@ -328,11 +328,11 @@ class TypeChecker
$var_type = null;
if ($gettype_position === self::ASSIGNMENT_TO_RIGHT) {
$var_name = StatementsChecker::getArrayVarId($conditional->right->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($conditional->right->args[0]->value, $this_class_name, $namespace, $aliased_classes);
$var_type = $conditional->left->value;
}
else if ($gettype_position === self::ASSIGNMENT_TO_LEFT) {
$var_name = StatementsChecker::getArrayVarId($conditional->left->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($conditional->left->args[0]->value, $this_class_name, $namespace, $aliased_classes);
$var_type = $conditional->right->value;
}
@ -348,10 +348,10 @@ class TypeChecker
if ($null_position !== null) {
if ($null_position === self::ASSIGNMENT_TO_RIGHT) {
$var_name = StatementsChecker::getArrayVarId($conditional->left);
$var_name = StatementsChecker::getArrayVarId($conditional->left, $this_class_name, $namespace, $aliased_classes);
}
else if ($null_position === self::ASSIGNMENT_TO_LEFT) {
$var_name = StatementsChecker::getArrayVarId($conditional->right);
$var_name = StatementsChecker::getArrayVarId($conditional->right, $this_class_name, $namespace, $aliased_classes);
}
else {
throw new \InvalidArgumentException('Bad null variable position');
@ -368,10 +368,10 @@ class TypeChecker
}
elseif ($false_position) {
if ($false_position === self::ASSIGNMENT_TO_RIGHT) {
$var_name = StatementsChecker::getArrayVarId($conditional->left);
$var_name = StatementsChecker::getArrayVarId($conditional->left, $this_class_name, $namespace, $aliased_classes);
}
else if ($false_position === self::ASSIGNMENT_TO_LEFT) {
$var_name = StatementsChecker::getArrayVarId($conditional->right);
$var_name = StatementsChecker::getArrayVarId($conditional->right, $this_class_name, $namespace, $aliased_classes);
}
else {
throw new \InvalidArgumentException('Bad null variable position');
@ -389,12 +389,12 @@ class TypeChecker
elseif ($true_position) {
if ($true_position === self::ASSIGNMENT_TO_RIGHT) {
if ($conditional->left instanceof PhpParser\Node\Expr\FuncCall) {
self::processFunctionCall($conditional->left, $if_types, true);
self::processFunctionCall($conditional->left, $if_types, true, $this_class_name, $namespace, $aliased_classes);
}
}
else if ($true_position === self::ASSIGNMENT_TO_LEFT) {
if ($conditional->right instanceof PhpParser\Node\Expr\FuncCall) {
self::processFunctionCall($conditional->right, $if_types, true);
self::processFunctionCall($conditional->right, $if_types, true, $this_class_name, $namespace, $aliased_classes);
}
}
else {
@ -403,17 +403,17 @@ class TypeChecker
}
}
elseif ($conditional instanceof PhpParser\Node\Expr\FuncCall) {
self::processFunctionCall($conditional, $if_types);
self::processFunctionCall($conditional, $if_types, false, $this_class_name, $namespace, $aliased_classes);
}
else if ($conditional instanceof PhpParser\Node\Expr\Empty_) {
$var_name = StatementsChecker::getArrayVarId($conditional->expr);
$var_name = StatementsChecker::getArrayVarId($conditional->expr, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = 'empty';
}
}
else if ($conditional instanceof PhpParser\Node\Expr\Isset_) {
foreach ($conditional->vars as $isset_var) {
$var_name = StatementsChecker::getArrayVarId($isset_var);
$var_name = StatementsChecker::getArrayVarId($isset_var, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = '!null';
}
@ -423,78 +423,84 @@ class TypeChecker
return $if_types;
}
protected static function processFunctionCall(PhpParser\Node\Expr\FuncCall $expr, array &$if_types, $negate = false)
{
protected static function processFunctionCall(
PhpParser\Node\Expr\FuncCall $expr,
array &$if_types,
$negate = false,
$this_class_name,
$namespace,
array $aliased_classes
) {
$prefix = $negate ? '!' : '';
if (self::hasNullCheck($expr)) {
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $prefix . 'null';
}
}
else if (self::hasIsACheck($expr)) {
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $prefix . $expr->args[1]->value->value;
}
}
else if (self::hasArrayCheck($expr)) {
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $prefix . 'array';
}
}
else if (self::hasBoolCheck($expr)) {
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $prefix . 'bool';
}
}
else if (self::hasStringCheck($expr)) {
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $prefix . 'string';
}
}
else if (self::hasObjectCheck($expr)) {
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $prefix . 'object';
}
}
else if (self::hasNumericCheck($expr)) {
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $prefix . 'numeric';
}
}
else if (self::hasIntCheck($expr)) {
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $prefix . 'int';
}
}
else if (self::hasFloatCheck($expr)) {
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $prefix . 'float';
}
}
else if (self::hasResourceCheck($expr)) {
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $prefix . 'resource';
}
}
else if (self::hasScalarCheck($expr)) {
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $prefix . 'scalar';
}
}
else if (self::hasCallableCheck($expr)) {
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value);
$var_name = StatementsChecker::getArrayVarId($expr->args[0]->value, $this_class_name, $namespace, $aliased_classes);
if ($var_name) {
$if_types[$var_name] = $prefix . 'callable';
}