diff --git a/src/Psalm/Checker/FunctionLikeChecker.php b/src/Psalm/Checker/FunctionLikeChecker.php index cfd7c6cb1..a7eb49359 100644 --- a/src/Psalm/Checker/FunctionLikeChecker.php +++ b/src/Psalm/Checker/FunctionLikeChecker.php @@ -613,6 +613,10 @@ abstract class FunctionLikeChecker implements StatementsSource $type_match_found = true; } + if ($param_type_part->isArray() && $input_type_part->isObjectLike()) { + $type_match_found = true; + } + if ($param_type_part->isScalar() && $input_type_part->isScalarType()) { $type_match_found = true; } diff --git a/src/Psalm/Checker/StatementsChecker.php b/src/Psalm/Checker/StatementsChecker.php index 786aaae39..2b0c9fbc9 100644 --- a/src/Psalm/Checker/StatementsChecker.php +++ b/src/Psalm/Checker/StatementsChecker.php @@ -1692,11 +1692,8 @@ class StatementsChecker } } - var_dump($item_value_type . ' ' . ($item_value_type->isSingle() ? ' yes': 'no')); - // if this array looks like an object-like array, let's return that instead if ($item_value_type && !$item_value_type->isSingle() && $item_key_type && $item_key_type->hasString() && !$item_key_type->hasInt()) { - var_dump('creating object-like array for ' . $item_value_type); $stmt->inferredType = new Type\Union([new Type\Atomic('object-like')]); return; } @@ -1829,7 +1826,7 @@ class StatementsChecker if ($iterator_type) { foreach ($iterator_type->types as $return_type) { // if it's an empty array, we cannot iterate over it - if ((string) $return_type === 'array') { + if ((string) $return_type === 'array') { continue; } @@ -1945,6 +1942,11 @@ class StatementsChecker continue; } + if (!isset($foreach_context->vars_in_scope[$var])) { + unset($context->vars_in_scope[$var]); + continue; + } + if ($foreach_context->vars_in_scope[$var]->isMixed()) { $context->vars_in_scope[$var] = $foreach_context->vars_in_scope[$var]; } @@ -2285,7 +2287,7 @@ class StatementsChecker if (isset($stmt->var->inferredType)) { $return_type = $stmt->var->inferredType; - if ($keyed_var_id) { + if ($keyed_array_var_id) { // when we have a pattern like // $a = []; // $a['b']['c']['d'] = 1; @@ -2309,10 +2311,9 @@ class StatementsChecker } $stmt->inferredType = $assignment_value_type; - - var_dump('checkArrayAssignment: setting ' . $keyed_array_var_id . ' to ' . $context->vars_in_scope[$keyed_array_var_id]); } - elseif (!$nesting) { + + if (!$nesting) { $assignment_type = new Type\Union([ new Type\Generic( 'array', @@ -2331,8 +2332,6 @@ class StatementsChecker } $context->vars_in_scope[$var_id] = $assignment_type; - - var_dump('checkArrayAssignment: setting ' . $var_id . ' to ' . $context->vars_in_scope[$var_id]); } } else { @@ -2365,7 +2364,6 @@ class StatementsChecker } if ($type->value === 'string' && $assignment_value_type->hasString() && !$assignment_key_type->hasString()) { - var_dump((string)$assignment_key_type . $line_number); return; } @@ -3680,8 +3678,6 @@ class StatementsChecker } if ($array_assignment) { - var_dump('checkArrayAccess: in array assignment'); - // if we're in an array assignment then we need to create some variables // e.g. // $a = []; @@ -3701,8 +3697,6 @@ class StatementsChecker } $stmt->inferredType = $keyed_assignment_type; - - var_dump('checkArrayAccess: setting ' . $keyed_array_var_id . ' to ' . $context->vars_in_scope[$keyed_array_var_id]); } if ($array_var_id === $var_id) { @@ -3725,8 +3719,6 @@ class StatementsChecker else { $context->vars_in_scope[$var_id] = $assignment_type; } - - var_dump('checkArrayAccess: setting ' . $var_id . ' to ' . $context->vars_in_scope[$var_id]); } if ($type->type_params[$value_index]->isEmpty()) { @@ -3777,6 +3769,10 @@ class StatementsChecker } } + if ($keyed_array_var_id && isset($context->vars_in_scope[$keyed_array_var_id])) { + $stmt->inferredType = $context->vars_in_scope[$keyed_array_var_id]; + } + if (!isset($stmt->inferredType)) { $stmt->inferredType = Type::getMixed(); } diff --git a/src/Psalm/Checker/TypeChecker.php b/src/Psalm/Checker/TypeChecker.php index 6b7e57903..bf7132afb 100644 --- a/src/Psalm/Checker/TypeChecker.php +++ b/src/Psalm/Checker/TypeChecker.php @@ -1049,10 +1049,11 @@ class TypeChecker } foreach ($simple_declared_types as $simple_declared_type) { - if ($simple_declared_type === 'mixed' || - ($simple_declared_type === 'object' && ClassLikeChecker::classOrInterfaceExists($differing_type)) || - ClassChecker::classExtendsOrImplements($differing_type, $simple_declared_type) || - (in_array($differing_type, ['float', 'int']) && in_array($simple_declared_type, ['float', 'int'])) + if ($simple_declared_type === 'mixed' + || ($simple_declared_type === 'object' && ClassLikeChecker::classOrInterfaceExists($differing_type)) + || ClassChecker::classExtendsOrImplements($differing_type, $simple_declared_type) + || (in_array($differing_type, ['array', 'object-like']) && in_array($simple_declared_type, ['array', 'object-like'])) + || (in_array($differing_type, ['float', 'int']) && in_array($simple_declared_type, ['float', 'int'])) ) { $is_match = true; break; diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 6c0b06c7b..a4c812033 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -39,6 +39,10 @@ class Atomic extends Type return true; } + if ($parent->hasType('array') && $this->isObjectLike()) { + return true; + } + if ($this->value === 'false' && $parent->hasType('bool')) { // this is fine return true;