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

Fix #1825 - improve behaviour of callable reconciliation

This commit is contained in:
Brown 2019-06-20 19:46:42 -04:00
parent db38270a6a
commit ac06ea659e
5 changed files with 87 additions and 5 deletions

View File

@ -865,6 +865,12 @@ class TypeAnalyzer
return true;
}
if ($input_type_part instanceof Type\Atomic\TCallableObject &&
$container_type_part instanceof TObject
) {
return true;
}
if ($container_type_part instanceof TNumeric &&
($input_type_part->isNumericType() || $input_type_part instanceof TString)
) {

View File

@ -911,6 +911,7 @@ class Reconciler
foreach ($existing_var_atomic_types as $type) {
if ($type instanceof TBool) {
$bool_types[] = $type;
$type->from_docblock = false;
} elseif ($type instanceof TScalar) {
$bool_types[] = new TBool;
$did_remove_type = true;
@ -942,6 +943,54 @@ class Reconciler
return Type::getMixed();
}
if ($new_var_type === 'string' && !$existing_var_type->hasMixed()) {
$string_types = [];
$did_remove_type = false;
foreach ($existing_var_atomic_types as $type) {
if ($type instanceof TString) {
$string_types[] = $type;
if (get_class($type) === TString::class) {
$type->from_docblock = false;
}
} elseif ($type instanceof TCallable) {
$string_types[] = new Type\Atomic\TCallableString;
$did_remove_type = true;
} elseif ($type instanceof TNumeric) {
$string_types[] = new TNumericString;
$did_remove_type = true;
} elseif ($type instanceof TScalar || $type instanceof TArrayKey) {
$string_types[] = new TString;
$did_remove_type = true;
} else {
$did_remove_type = true;
}
}
if ((!$did_remove_type || !$string_types) && !$is_equality) {
if ($key && $code_location) {
self::triggerIssueForImpossible(
$existing_var_type,
$old_var_type_string,
$key,
$new_var_type,
!$did_remove_type,
$code_location,
$suppressed_issues
);
}
}
if ($string_types) {
return new Type\Union($string_types);
}
$failed_reconciliation = 2;
return Type::getMixed();
}
$is_maybe_callable_array = false;
if ($new_var_type === 'array' && isset($existing_var_atomic_types['callable'])) {
@ -950,14 +999,14 @@ class Reconciler
if (!isset($existing_var_atomic_types['array'])) {
$existing_var_type->addType(
new TCallableObjectLikeArray([
Type::getMixed(),
new Type\Union([new TObject, new TString]),
Type::getString()
])
);
} else {
$array_combination = \Psalm\Internal\Type\TypeCombination::combineTypes([
new TCallableObjectLikeArray([
Type::getMixed(),
new Type\Union([new TObject, new TString]),
Type::getString()
]),
$existing_var_atomic_types['array']

View File

@ -874,8 +874,8 @@ class CallableTest extends TestCase
* @psalm-suppress MixedArgument
*/
function foo($c) : void {
if (is_array($c)) {
echo $c[2];
if (is_array($c) && is_string($c[1])) {
echo $c[1];
}
}',
],

View File

@ -272,7 +272,20 @@ class RedundantConditionTest extends TestCase
'assertions' => [],
'error_levels' => ['MixedAssignment', 'MixedArrayAccess'],
],
'hardPhpTypeAssertionsOnDocblockType' => [
'hardPhpTypeAssertionsOnDocblockBoolType' => [
'<?php
/** @param bool|null $bar */
function foo($bar): void {
if (!is_null($bar) && !is_bool($bar)) {
throw new \Exception("bad");
}
if ($bar !== null) {}
}',
'assertions' => [],
'error_levels' => ['DocblockTypeContradiction'],
],
'hardPhpTypeAssertionsOnDocblockStringType' => [
'<?php
/** @param string|null $bar */
function foo($bar): void {

View File

@ -1363,6 +1363,20 @@ class TypeReconciliationTest extends TestCase
}
}',
],
'reconcileCallable' => [
'<?php
function reflectCallable(callable $callable): ReflectionFunctionAbstract {
if (\is_array($callable)) {
return new \ReflectionMethod($callable[0], $callable[1]);
} elseif ($callable instanceof \Closure || \is_string($callable)) {
return new \ReflectionFunction($callable);
} elseif (\is_object($callable)) {
return new \ReflectionMethod($callable, "__invoke");
} else {
throw new \InvalidArgumentException("Bad");
}
}'
],
];
}