1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Support int offsets in dealing with type inference

This commit is contained in:
Matthew Brown 2016-12-10 18:24:28 -05:00
parent 327e3150a2
commit 73b1ab1411
7 changed files with 127 additions and 17 deletions

View File

@ -510,7 +510,11 @@ class AssignmentChecker
$class_property_type = Type::getMixed();
} else {
$class_property_type = ExpressionChecker::fleshOutTypes($class_property_type, [], $lhs_type_part->value);
$class_property_type = ExpressionChecker::fleshOutTypes(
clone $class_property_type,
[],
$lhs_type_part->value
);
}
$class_property_types[] = $class_property_type;

View File

@ -660,9 +660,12 @@ class FetchChecker
$statements_checker->getAliasedClasses()
);
$keyed_array_var_id = $array_var_id && $stmt->dim instanceof PhpParser\Node\Scalar\String_
? $array_var_id . '[\'' . $stmt->dim->value . '\']'
: null;
$keyed_array_var_id = ExpressionChecker::getArrayVarId(
$stmt,
$statements_checker->getFQCLN(),
$statements_checker->getNamespace(),
$statements_checker->getAliasedClasses()
);
if ($stmt->dim && ExpressionChecker::check($statements_checker, $stmt->dim, $context) === false) {
return false;

View File

@ -923,7 +923,11 @@ class ExpressionChecker
$stmt->dim instanceof PhpParser\Node\Scalar\LNumber)
) {
$root_var_id = self::getArrayVarId($stmt->var, $this_class_name, $namespace, $aliased_classes);
return $root_var_id ? $root_var_id . '[\'' . $stmt->dim->value . '\']' : null;
$offset = $stmt->dim instanceof PhpParser\Node\Scalar\String_
? '\'' . $stmt->dim->value . '\''
: $stmt->dim->value;
return $root_var_id ? $root_var_id . '[' . $offset . ']' : null;
}
return self::getVarId($stmt, $this_class_name, $namespace, $aliased_classes);

View File

@ -1092,6 +1092,10 @@ class TypeChecker
? clone $existing_types[$key]
: self::getValueForKey($key, $existing_types);
if ($result_type && empty($result_type->types)) {
throw new \InvalidArgumentException('Union::$types cannot be empty after get value for ' . $key);
}
foreach ($new_type_parts as $new_type_part) {
$result_type = self::reconcileTypes(
(string) $new_type_part,
@ -1205,9 +1209,9 @@ class TypeChecker
)) {
return false;
}
return Type::getMixed();
}
return Type::getMixed();
}
return $existing_var_type;
@ -1315,7 +1319,7 @@ class TypeChecker
*/
protected static function getArrayValueForKey($key, array &$existing_keys)
{
$key_parts = preg_split('/(\'\]|\[\')/', $key, -1, PREG_SPLIT_NO_EMPTY);
$key_parts = preg_split('/(\]|\[)/', $key, -1, PREG_SPLIT_NO_EMPTY);
if (count($key_parts) === 1) {
return isset($existing_keys[$key_parts[0]]) ? clone $existing_keys[$key_parts[0]] : null;
@ -1330,30 +1334,35 @@ class TypeChecker
// for an expression like $obj->key1->key2
for ($i = 1; $i < count($key_parts); $i++) {
$new_base_key = $base_key . '[\'' . $key_parts[$i] . '\']';
$new_base_key = $base_key . '[' . $key_parts[$i] . ']';
if (!isset($existing_keys[$new_base_key])) {
/** @var Type\Union|null */
$new_base_type = null;
foreach ($existing_keys[$base_key]->types as $existing_key_type_part) {
if (!$existing_key_type_part->isObjectLike()) {
if ($existing_key_type_part instanceof Type\Generic) {
$new_base_type_candidate = clone $existing_key_type_part->type_params[1];
} elseif (!$existing_key_type_part instanceof Type\ObjectLike) {
return null;
}
} else {
$array_properties = $existing_key_type_part->properties;
/** @var Type\ObjectLike $existing_key_type_part */
$array_properties = $existing_key_type_part->properties;
$key_parts_key = str_replace('\'', '', $key_parts[$i]);
if (!isset($array_properties[$key_parts[$i]])) {
return null;
if (!isset($array_properties[$key_parts_key])) {
return null;
}
$new_base_type_candidate = clone $array_properties[$key_parts_key];
}
if (!$new_base_type) {
$new_base_type = clone $array_properties[$key_parts[$i]];
$new_base_type = $new_base_type_candidate;
} else {
$new_base_type = Type::combineUnionTypes(
$new_base_type,
clone $array_properties[$key_parts[$i]]
$new_base_type_candidate
);
}

View File

@ -86,4 +86,11 @@ class Generic extends Atomic
) .
'>';
}
public function __clone()
{
foreach ($this->type_params as &$type_param) {
$type_param = clone $type_param;
}
}
}

View File

@ -92,4 +92,11 @@ class ObjectLike extends Atomic
return Type::combineTypes($all_types);
}
public function __clone()
{
foreach ($this->properties as $key => &$property) {
$property = clone $property;
}
}
}

View File

@ -4,6 +4,7 @@ namespace Psalm\Tests;
use PhpParser\ParserFactory;
use PHPUnit_Framework_TestCase;
use Psalm\Context;
use Psalm\Type;
class ArrayAssignmentTest extends PHPUnit_Framework_TestCase
{
@ -493,4 +494,79 @@ class ArrayAssignmentTest extends PHPUnit_Framework_TestCase
$context = new Context('somefile.php');
$file_checker->check(true, true, $context);
}
public function testInstanceOfStringOffset()
{
$stmts = self::$parser->parse('<?php
class A {
public function foo() : void { }
}
function bar (array $a) : void {
if ($a["a"] instanceof A) {
$a["a"]->foo();
}
}
');
$file_checker = new \Psalm\Checker\FileChecker('somefile.php', $stmts);
$context = new Context('somefile.php');
$file_checker->check(true, true, $context);
}
public function testInstanceOfIntOffset()
{
$context = new Context('somefile.php');
$stmts = self::$parser->parse('<?php
class A {
public function foo() : void { }
}
function bar (array $a) : void {
if ($a[0] instanceof A) {
$a[0]->foo();
}
}
');
$file_checker = new \Psalm\Checker\FileChecker('somefile.php', $stmts);
$file_checker->check(true, true, $context);
}
public function testNotEmptyStringOffset()
{
$context = new Context('somefile.php');
$stmts = self::$parser->parse('<?php
/**
* @param array<string> $a
*/
function bar (array $a) : string {
if ($a["bat"]) {
return $a["bat"];
}
return "blah";
}
');
$file_checker = new \Psalm\Checker\FileChecker('somefile.php', $stmts);
$file_checker->check(true, true, $context);
}
public function testNotEmptyIntOffset()
{
$context = new Context('somefile.php');
$stmts = self::$parser->parse('<?php
/**
* @param array<string> $a
*/
function bar (array $a) : string {
if ($a[0]) {
return $a[0];
}
return "blah";
}
');
$file_checker = new \Psalm\Checker\FileChecker('somefile.php', $stmts);
$file_checker->check(true, true, $context);
}
}