From 05f28ce8cd2fc0e365da9756c8d7d8b559695326 Mon Sep 17 00:00:00 2001 From: Peter van Dommelen Date: Sun, 18 Sep 2022 17:02:09 +0200 Subject: [PATCH 1/2] Do not widen type to `mixed` and lose the existing type information when an `Any` assertion is used. Fixes #8084. --- .../Internal/Type/AssertionReconciler.php | 4 ++++ tests/TypeReconciliation/IssetTest.php | 18 ++++++++++++++++++ tests/TypeReconciliation/ReconcilerTest.php | 2 ++ 3 files changed, 24 insertions(+) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 551db70235c..3778bbb5a45 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -293,6 +293,10 @@ private static function refine( $old_var_type_string = $existing_var_type->getId(); + if ($new_type_part instanceof TMixed) { + return $existing_var_type; + } + $new_type_has_interface = false; if ($new_type_part->isObjectType()) { diff --git a/tests/TypeReconciliation/IssetTest.php b/tests/TypeReconciliation/IssetTest.php index e623b25d677..95bc2cceeba 100644 --- a/tests/TypeReconciliation/IssetTest.php +++ b/tests/TypeReconciliation/IssetTest.php @@ -188,6 +188,24 @@ function foo(array $arr) : int { return $arr[$b]; }', ], + 'issetWithCalculatedKeyAndEqualComparison' => [ + 'code' => ' $array */ + $array = []; + + function sameString(string $string): string { + return $string; + } + + if (isset($array[sameString("key")]) === false) { + throw new \LogicException("No such key"); + } + $value = $array[sameString("key")]; + ', + 'assertions' => [ + '$value' => 'string', + ], + ], 'issetArrayOffsetConditionalCreationWithInt' => [ 'code' => ' $arr */ diff --git a/tests/TypeReconciliation/ReconcilerTest.php b/tests/TypeReconciliation/ReconcilerTest.php index 1d8ebde693d..4f86dbbc9c0 100644 --- a/tests/TypeReconciliation/ReconcilerTest.php +++ b/tests/TypeReconciliation/ReconcilerTest.php @@ -9,6 +9,7 @@ use Psalm\Internal\Type\AssertionReconciler; use Psalm\Internal\Type\Comparator\UnionTypeComparator; use Psalm\Storage\Assertion; +use Psalm\Storage\Assertion\Any; use Psalm\Storage\Assertion\Falsy; use Psalm\Storage\Assertion\IsIdentical; use Psalm\Storage\Assertion\IsLooselyEqual; @@ -179,6 +180,7 @@ public function providerTestReconcilation(): array 'SimpleXMLElementNotAlwaysTruthy2' => ['SimpleXMLElement', new Falsy(), 'SimpleXMLElement'], 'SimpleXMLIteratorNotAlwaysTruthy' => ['SimpleXMLIterator', new Truthy(), 'SimpleXMLIterator'], 'SimpleXMLIteratorNotAlwaysTruthy2' => ['SimpleXMLIterator', new Falsy(), 'SimpleXMLIterator'], + 'stringWithAny' => ['string', new Any(), 'string'], ]; } From 2eb4ca0e3be42ae1ff00a548401baebe1e653115 Mon Sep 17 00:00:00 2001 From: Peter van Dommelen Date: Sun, 18 Sep 2022 17:07:30 +0200 Subject: [PATCH 2/2] Within the reconciler's fast path, do not require the existing type to include mixed when handling an `Any` assertion. --- src/Psalm/Internal/Type/SimpleAssertionReconciler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index 329d7def1b4..62a0473fdb8 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -105,7 +105,7 @@ public static function reconcile( int &$failed_reconciliation = Reconciler::RECONCILIATION_OK, bool $inside_loop = false ): ?Union { - if ($assertion instanceof Any && $existing_var_type->hasMixed()) { + if ($assertion instanceof Any) { return $existing_var_type; }