Skip to content
Permalink
Browse files

Fix #1986 - do better inference after isset on array offsets

  • Loading branch information...
muglug committed Aug 11, 2019
1 parent 585fffa commit e32b92be6bae8db7e9859d7cacc37c5be5225f25
@@ -267,6 +267,28 @@ public static function reconcile(
);
}
if ($assertion === 'string-array-access') {
return self::reconcileStringArrayAccess(
$codebase,
$existing_var_type,
$key,
$code_location,
$suppressed_issues,
$failed_reconciliation
);
}
if ($assertion === 'int-or-string-array-access') {
return self::reconcileIntArrayAccess(
$codebase,
$existing_var_type,
$key,
$code_location,
$suppressed_issues,
$failed_reconciliation
);
}
if ($assertion === 'numeric' && !$existing_var_type->hasMixed()) {
return self::reconcileNumeric(
$existing_var_type,
@@ -1332,6 +1354,126 @@ private static function reconcileArray(
return Type::getMixed();
}
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
*/
private static function reconcileStringArrayAccess(
Codebase $codebase,
Union $existing_var_type,
?string $key,
?CodeLocation $code_location,
array $suppressed_issues,
int &$failed_reconciliation
) : Union {
$old_var_type_string = $existing_var_type->getId();
$existing_var_atomic_types = $existing_var_type->getTypes();
if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) {
return new Union([
new Atomic\TNonEmptyArray([Type::getArrayKey(), Type::getMixed()]),
new TNamedObject('ArrayAccess'),
]);
}
$array_types = [];
foreach ($existing_var_atomic_types as $type) {
if ($type->isArrayAccessibleWithStringKey($codebase)) {
if (get_class($type) === TArray::class) {
$array_types[] = new Atomic\TNonEmptyArray($type->type_params);
} else {
$array_types[] = $type;
}
} elseif ($type instanceof TTemplateParam) {
$array_types[] = $type;
}
}
if (!$array_types) {
if ($key && $code_location) {
self::triggerIssueForImpossible(
$existing_var_type,
$old_var_type_string,
$key,
'string-array-access',
true,
$code_location,
$suppressed_issues
);
}
}
if ($array_types) {
return new Type\Union($array_types);
}
$failed_reconciliation = 2;
return Type::getMixed();
}
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
*/
private static function reconcileIntArrayAccess(
Codebase $codebase,
Union $existing_var_type,
?string $key,
?CodeLocation $code_location,
array $suppressed_issues,
int &$failed_reconciliation
) : Union {
$old_var_type_string = $existing_var_type->getId();
$existing_var_atomic_types = $existing_var_type->getTypes();
if ($existing_var_type->hasMixed()) {
return new Union([
new Atomic\TNonEmptyArray([Type::getArrayKey(), Type::getMixed()]),
new TNamedObject('ArrayAccess'),
]);
}
$array_types = [];
foreach ($existing_var_atomic_types as $type) {
if ($type->isArrayAccessibleWithIntOrStringKey($codebase)) {
if (get_class($type) === TArray::class) {
$array_types[] = new Atomic\TNonEmptyArray($type->type_params);
} else {
$array_types[] = $type;
}
} elseif ($type instanceof TTemplateParam) {
$array_types[] = $type;
}
}
if (!$array_types) {
if ($key && $code_location) {
self::triggerIssueForImpossible(
$existing_var_type,
$old_var_type_string,
$key,
'int-or-string-array-access',
true,
$code_location,
$suppressed_issues
);
}
}
if ($array_types) {
return new Type\Union($array_types);
}
$failed_reconciliation = 2;
return Type::getMixed();
}
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
@@ -1941,7 +1941,7 @@ private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, $fake_m
$param_index = \array_search($param_name, \array_keys($storage->param_types), true);
if (!isset($storage->params[$param_index]->type)) {
if ($param_index === false || !isset($storage->params[$param_index]->type)) {
continue;
}
@@ -341,6 +341,54 @@ function (Atomic $a) use ($codebase) : bool {
);
}
/**
* @return bool
*/
public function isArrayAccessibleWithStringKey(Codebase $codebase)
{
return $this instanceof TArray
|| $this instanceof ObjectLike
|| $this->hasArrayAccessInterface($codebase)
|| ($this instanceof TNamedObject && $this->value === 'SimpleXMLElement');
}
/**
* @return bool
*/
public function isArrayAccessibleWithIntOrStringKey(Codebase $codebase)
{
return $this instanceof TString
|| $this->isArrayAccessibleWithStringKey($codebase);
}
/**
* @return bool
*/
private function hasArrayAccessInterface(Codebase $codebase)
{
return $this instanceof TNamedObject
&& (
strtolower($this->value) === 'arrayaccess'
|| ($codebase->classOrInterfaceExists($this->value)
&& ($codebase->classExtendsOrImplements(
$this->value,
'ArrayAccess'
) || $codebase->interfaceExtends(
$this->value,
'ArrayAccess'
)))
|| (
$this->extra_types
&& array_filter(
$this->extra_types,
function (Atomic $a) use ($codebase) : bool {
return $a->hasArrayAccessInterface($codebase);
}
)
)
);
}
/**
* @param StatementsSource $source
* @param CodeLocation $code_location
@@ -102,12 +102,12 @@ public static function reconcileKeyedTypes(
$base_key = array_shift($key_parts);
if (!isset($new_types[$base_key])) {
$new_types[$base_key] = [['!~bool'], ['!~int'], ['=isset']];
} else {
$new_types[$base_key][] = ['!~bool'];
$new_types[$base_key][] = ['!~int'];
$new_types[$base_key][] = ['=isset'];
if (!isset($existing_types[$base_key]) || $existing_types[$base_key]->isNullable()) {
if (!isset($new_types[$base_key])) {
$new_types[$base_key] = [['=isset']];
} else {
$new_types[$base_key][] = ['=isset'];
}
}
while ($key_parts) {
@@ -120,12 +120,17 @@ public static function reconcileKeyedTypes(
$new_base_key = $base_key . '[' . $array_key . ']';
if (strpos($array_key, '\'') !== false) {
$new_types[$base_key][] = ['!string'];
$new_types[$base_key][] = ['!=falsy'];
$new_types[$base_key][] = ['=string-array-access'];
} else {
$new_types[$base_key][] = ['=int-or-string-array-access'];
}
$base_key = $new_base_key;
} elseif ($divider === '->') {
continue;
}
if ($divider === '->') {
$property_name = array_shift($key_parts);
$new_base_key = $base_key . '->' . $property_name;
@@ -164,8 +164,8 @@ function takesA(?A $a): A {
'issetVariableKeysWithoutChange' => [
'<?php
$arr = [[1, 2, 3], null, [1, 2, 3], null];
$b = 2;
$c = 0;
$b = rand(0, 2);
$c = rand(0, 2);
if (isset($arr[$b][$c])) {
echo $arr[$b][$c];
}',
@@ -1418,6 +1418,47 @@ function foo(array $a) : void {
if ($a[0] === 5) {}
}'
],
'assertHasArrayAccess' => [
'<?php
/**
* @return array|ArrayAccess
*/
function getBar(array $array) {
if (isset($array[\'foo\'][\'bar\'])) {
return $array[\'foo\'];
}
return [];
}',
],
'assertHasArrayAccessWithType' => [
'<?php
/**
* @param array<string, array<string, string>> $array
* @return array<string, string>
*/
function getBar(array $array) : array {
if (isset($array[\'foo\'][\'bar\'])) {
return $array[\'foo\'];
}
return [];
}',
],
'assertHasArrayAccessOnSimpleXMLElement' => [
'<?php
function getBar(SimpleXMLElement $e, string $s) : void {
if (isset($e[$s])) {
echo (string) $e[$s];
}
if (isset($e[\'foo\'])) {
echo (string) $e[\'foo\'];
}
if (isset($e->bar)) {}
}',
],
];
}

0 comments on commit e32b92b

Please sign in to comment.
You can’t perform that action at this time.