mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 05:41:20 +01:00
Fix issue re-asserting non-empty-mixed
This commit is contained in:
parent
fb19e510a7
commit
d9945a0ced
@ -906,6 +906,7 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
|
|||||||
if ($var_id
|
if ($var_id
|
||||||
&& $class_type
|
&& $class_type
|
||||||
&& $has_valid_method_call_type
|
&& $has_valid_method_call_type
|
||||||
|
&& !$class_type->hasMixed()
|
||||||
&& !$invalid_method_call_types
|
&& !$invalid_method_call_types
|
||||||
&& $existent_method_ids
|
&& $existent_method_ids
|
||||||
&& ($class_type->from_docblock || $class_type->isNullable())
|
&& ($class_type->from_docblock || $class_type->isNullable())
|
||||||
|
@ -1208,6 +1208,41 @@ class Reconciler
|
|||||||
|| $existing_var_type->possibly_undefined
|
|| $existing_var_type->possibly_undefined
|
||||||
|| $existing_var_type->possibly_undefined_from_try;
|
|| $existing_var_type->possibly_undefined_from_try;
|
||||||
|
|
||||||
|
if ($is_strict_equality && $new_var_type === 'empty') {
|
||||||
|
$existing_var_type->removeType('null');
|
||||||
|
$existing_var_type->removeType('false');
|
||||||
|
|
||||||
|
if ($existing_var_type->hasType('array')
|
||||||
|
&& $existing_var_type->getTypes()['array']->getId() === 'array<empty, empty>'
|
||||||
|
) {
|
||||||
|
$existing_var_type->removeType('array');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existing_var_type->hasMixed()) {
|
||||||
|
$existing_var_type->removeType('mixed');
|
||||||
|
|
||||||
|
if (!$existing_var_atomic_types['mixed'] instanceof Type\Atomic\TEmptyMixed) {
|
||||||
|
$existing_var_type->addType(new Type\Atomic\TNonEmptyMixed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self::removeFalsyNegatedLiteralTypes(
|
||||||
|
$existing_var_type,
|
||||||
|
$did_remove_type
|
||||||
|
);
|
||||||
|
|
||||||
|
$existing_var_type->possibly_undefined = false;
|
||||||
|
$existing_var_type->possibly_undefined_from_try = false;
|
||||||
|
|
||||||
|
if ($existing_var_type->getTypes()) {
|
||||||
|
return $existing_var_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
$failed_reconciliation = true;
|
||||||
|
|
||||||
|
return Type::getMixed();
|
||||||
|
}
|
||||||
|
|
||||||
if ($existing_var_type->hasMixed()) {
|
if ($existing_var_type->hasMixed()) {
|
||||||
if ($existing_var_type->isMixed()
|
if ($existing_var_type->isMixed()
|
||||||
&& $existing_var_atomic_types['mixed'] instanceof Type\Atomic\TEmptyMixed
|
&& $existing_var_atomic_types['mixed'] instanceof Type\Atomic\TEmptyMixed
|
||||||
@ -1257,28 +1292,6 @@ class Reconciler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($is_strict_equality && $new_var_type === 'empty') {
|
|
||||||
$existing_var_type->removeType('null');
|
|
||||||
$existing_var_type->removeType('false');
|
|
||||||
|
|
||||||
if ($existing_var_type->hasType('array')
|
|
||||||
&& $existing_var_type->getTypes()['array']->getId() === 'array<empty, empty>'
|
|
||||||
) {
|
|
||||||
$existing_var_type->removeType('array');
|
|
||||||
}
|
|
||||||
|
|
||||||
$existing_var_type->possibly_undefined = false;
|
|
||||||
$existing_var_type->possibly_undefined_from_try = false;
|
|
||||||
|
|
||||||
if ($existing_var_type->getTypes()) {
|
|
||||||
return $existing_var_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
$failed_reconciliation = true;
|
|
||||||
|
|
||||||
return Type::getMixed();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($existing_var_type->hasType('null')) {
|
if ($existing_var_type->hasType('null')) {
|
||||||
$did_remove_type = true;
|
$did_remove_type = true;
|
||||||
$existing_var_type->removeType('null');
|
$existing_var_type->removeType('null');
|
||||||
@ -1295,59 +1308,10 @@ class Reconciler
|
|||||||
$existing_var_type->addType(new TTrue);
|
$existing_var_type->addType(new TTrue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($existing_var_type->hasString()) {
|
self::removeFalsyNegatedLiteralTypes(
|
||||||
$existing_string_types = $existing_var_type->getLiteralStrings();
|
$existing_var_type,
|
||||||
|
$did_remove_type
|
||||||
if ($existing_string_types) {
|
);
|
||||||
foreach ($existing_string_types as $string_key => $literal_type) {
|
|
||||||
if (!$literal_type->value) {
|
|
||||||
$existing_var_type->removeType($string_key);
|
|
||||||
$did_remove_type = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$did_remove_type = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($existing_var_type->hasInt()) {
|
|
||||||
$existing_int_types = $existing_var_type->getLiteralInts();
|
|
||||||
|
|
||||||
if ($existing_int_types) {
|
|
||||||
foreach ($existing_int_types as $int_key => $literal_type) {
|
|
||||||
if (!$literal_type->value) {
|
|
||||||
$existing_var_type->removeType($int_key);
|
|
||||||
$did_remove_type = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$did_remove_type = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($existing_var_type->hasType('array')) {
|
|
||||||
$array_atomic_type = $existing_var_type->getTypes()['array'];
|
|
||||||
|
|
||||||
if ($array_atomic_type instanceof Type\Atomic\TArray
|
|
||||||
&& !$array_atomic_type instanceof Type\Atomic\TNonEmptyArray
|
|
||||||
) {
|
|
||||||
$did_remove_type = true;
|
|
||||||
|
|
||||||
if ($array_atomic_type->getId() === 'array<empty, empty>') {
|
|
||||||
$existing_var_type->removeType('array');
|
|
||||||
} else {
|
|
||||||
$existing_var_type->addType(
|
|
||||||
new Type\Atomic\TNonEmptyArray(
|
|
||||||
$array_atomic_type->type_params
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} elseif ($array_atomic_type instanceof Type\Atomic\ObjectLike
|
|
||||||
&& !$array_atomic_type->sealed
|
|
||||||
) {
|
|
||||||
$did_remove_type = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$existing_var_type->possibly_undefined = false;
|
$existing_var_type->possibly_undefined = false;
|
||||||
$existing_var_type->possibly_undefined_from_try = false;
|
$existing_var_type->possibly_undefined_from_try = false;
|
||||||
@ -1500,6 +1464,68 @@ class Reconciler
|
|||||||
return $existing_var_type;
|
return $existing_var_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function removeFalsyNegatedLiteralTypes(
|
||||||
|
Type\Union $existing_var_type,
|
||||||
|
bool &$did_remove_type
|
||||||
|
) {
|
||||||
|
if ($existing_var_type->hasString()) {
|
||||||
|
$existing_string_types = $existing_var_type->getLiteralStrings();
|
||||||
|
|
||||||
|
if ($existing_string_types) {
|
||||||
|
foreach ($existing_string_types as $string_key => $literal_type) {
|
||||||
|
if (!$literal_type->value) {
|
||||||
|
$existing_var_type->removeType($string_key);
|
||||||
|
$did_remove_type = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$did_remove_type = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existing_var_type->hasInt()) {
|
||||||
|
$existing_int_types = $existing_var_type->getLiteralInts();
|
||||||
|
|
||||||
|
if ($existing_int_types) {
|
||||||
|
foreach ($existing_int_types as $int_key => $literal_type) {
|
||||||
|
if (!$literal_type->value) {
|
||||||
|
$existing_var_type->removeType($int_key);
|
||||||
|
$did_remove_type = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$did_remove_type = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existing_var_type->hasType('array')) {
|
||||||
|
$array_atomic_type = $existing_var_type->getTypes()['array'];
|
||||||
|
|
||||||
|
if ($array_atomic_type instanceof Type\Atomic\TArray
|
||||||
|
&& !$array_atomic_type instanceof Type\Atomic\TNonEmptyArray
|
||||||
|
) {
|
||||||
|
$did_remove_type = true;
|
||||||
|
|
||||||
|
if ($array_atomic_type->getId() === 'array<empty, empty>') {
|
||||||
|
$existing_var_type->removeType('array');
|
||||||
|
} else {
|
||||||
|
$existing_var_type->addType(
|
||||||
|
new Type\Atomic\TNonEmptyArray(
|
||||||
|
$array_atomic_type->type_params
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} elseif ($array_atomic_type instanceof Type\Atomic\ObjectLike
|
||||||
|
&& !$array_atomic_type->sealed
|
||||||
|
) {
|
||||||
|
$did_remove_type = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $new_var_type
|
* @param string $new_var_type
|
||||||
* @param int $bracket_pos
|
* @param int $bracket_pos
|
||||||
|
@ -168,6 +168,23 @@ class ArrayAccessTest extends TestCase
|
|||||||
'assertions' => [],
|
'assertions' => [],
|
||||||
'error_levels' => ['PossiblyUndefinedArrayOffset'],
|
'error_levels' => ['PossiblyUndefinedArrayOffset'],
|
||||||
],
|
],
|
||||||
|
'noRedundantConditionOnMixedArrayAccess' => [
|
||||||
|
'<?php
|
||||||
|
/** @var array<int, int> */
|
||||||
|
$b = [];
|
||||||
|
|
||||||
|
/** @var array<int, int> */
|
||||||
|
$c = [];
|
||||||
|
|
||||||
|
/** @var array<int, mixed> */
|
||||||
|
$d = [];
|
||||||
|
|
||||||
|
if (!empty($d[0]) && !isset($c[$d[0]])) {
|
||||||
|
if (isset($b[$d[0]])) {}
|
||||||
|
}',
|
||||||
|
[],
|
||||||
|
'error_levels' => ['MixedArrayOffset'],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +202,25 @@ class MethodCallTest extends TestCase
|
|||||||
$db = new PDO("sqlite:sqlitedb");
|
$db = new PDO("sqlite:sqlitedb");
|
||||||
$db->sqliteCreateFunction("md5rev", "md5_and_reverse", 1);',
|
$db->sqliteCreateFunction("md5rev", "md5_and_reverse", 1);',
|
||||||
],
|
],
|
||||||
|
'dontConvertedMaybeMixedAfterCall' => [
|
||||||
|
'<?php
|
||||||
|
class B {
|
||||||
|
public function foo() : void {}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param array<B> $b
|
||||||
|
*/
|
||||||
|
function foo(array $a, array $b) : void {
|
||||||
|
$c = array_merge($b, $a);
|
||||||
|
|
||||||
|
foreach ($c as $d) {
|
||||||
|
$d->foo();
|
||||||
|
if ($d instanceof B) {}
|
||||||
|
}
|
||||||
|
}',
|
||||||
|
[],
|
||||||
|
'error_levels' => ['MixedAssignment', 'MixedMethodCall'],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user