From ac06ea659efa0ee05b8d9889e394d2e6b9b0db38 Mon Sep 17 00:00:00 2001 From: Brown Date: Thu, 20 Jun 2019 19:46:42 -0400 Subject: [PATCH] Fix #1825 - improve behaviour of callable reconciliation --- src/Psalm/Internal/Analyzer/TypeAnalyzer.php | 6 +++ src/Psalm/Type/Reconciler.php | 53 +++++++++++++++++++- tests/CallableTest.php | 4 +- tests/RedundantConditionTest.php | 15 +++++- tests/TypeReconciliationTest.php | 14 ++++++ 5 files changed, 87 insertions(+), 5 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/TypeAnalyzer.php b/src/Psalm/Internal/Analyzer/TypeAnalyzer.php index cbb4bd18a..3a1026c2b 100644 --- a/src/Psalm/Internal/Analyzer/TypeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/TypeAnalyzer.php @@ -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) ) { diff --git a/src/Psalm/Type/Reconciler.php b/src/Psalm/Type/Reconciler.php index e1933e4c0..7acc6d1af 100644 --- a/src/Psalm/Type/Reconciler.php +++ b/src/Psalm/Type/Reconciler.php @@ -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'] diff --git a/tests/CallableTest.php b/tests/CallableTest.php index 32ce27446..a7f95bbf8 100644 --- a/tests/CallableTest.php +++ b/tests/CallableTest.php @@ -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]; } }', ], diff --git a/tests/RedundantConditionTest.php b/tests/RedundantConditionTest.php index b0b26444d..9e30ec55c 100644 --- a/tests/RedundantConditionTest.php +++ b/tests/RedundantConditionTest.php @@ -272,7 +272,20 @@ class RedundantConditionTest extends TestCase 'assertions' => [], 'error_levels' => ['MixedAssignment', 'MixedArrayAccess'], ], - 'hardPhpTypeAssertionsOnDocblockType' => [ + 'hardPhpTypeAssertionsOnDocblockBoolType' => [ + ' [], + 'error_levels' => ['DocblockTypeContradiction'], + ], + 'hardPhpTypeAssertionsOnDocblockStringType' => [ ' [ + '