From 6ea950c2eb307534d8985136e91da1f6ad0b3428 Mon Sep 17 00:00:00 2001 From: orklah Date: Mon, 3 Jan 2022 23:40:39 +0100 Subject: [PATCH 1/3] Identify ints from TClassConstant when checking for LiteralEquality --- .../Internal/Type/AssertionReconciler.php | 328 +++++++++++------- 1 file changed, 195 insertions(+), 133 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index e55661873aa..9cbabb6d257 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -20,6 +20,7 @@ use Psalm\Type; use Psalm\Type\Atomic; use Psalm\Type\Atomic\TArray; +use Psalm\Type\Atomic\TClassConstant; use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TEnumCase; use Psalm\Type\Atomic\TFloat; @@ -189,6 +190,7 @@ public static function reconcile( if ($bracket_pos) { return self::handleLiteralEquality( + $statements_analyzer, $assertion, $bracket_pos, $is_loose_equality, @@ -958,18 +960,19 @@ private static function filterTypeWithAnother( } /** - * @param string[] $suppressed_issues + * @param string[] $suppressed_issues */ private static function handleLiteralEquality( - string $assertion, - int $bracket_pos, - bool $is_loose_equality, - Union $existing_var_type, - string $old_var_type_string, - ?string $var_id, - bool $negated, - ?CodeLocation $code_location, - array $suppressed_issues + StatementsAnalyzer $statements_analyzer, + string $assertion, + int $bracket_pos, + bool $is_loose_equality, + Union $existing_var_type, + string $old_var_type_string, + ?string $var_id, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues ): Union { $value = substr($assertion, $bracket_pos + 1, -1); @@ -978,129 +981,18 @@ private static function handleLiteralEquality( $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); if ($scalar_type === 'int') { - $value = (int) $value; - - if ($existing_var_type->hasMixed() - || $existing_var_type->hasScalar() - || $existing_var_type->hasNumeric() - || $existing_var_type->hasArrayKey() - ) { - if ($is_loose_equality) { - return $existing_var_type; - } - - return new Union([new TLiteralInt($value)]); - } - - $has_int = false; - - foreach ($existing_var_atomic_types as $existing_var_atomic_type) { - if ($existing_var_atomic_type instanceof TInt) { - $has_int = true; - } elseif ($existing_var_atomic_type instanceof TTemplateParam) { - if ($existing_var_atomic_type->as->hasMixed() - || $existing_var_atomic_type->as->hasScalar() - || $existing_var_atomic_type->as->hasNumeric() - || $existing_var_atomic_type->as->hasArrayKey() - ) { - if ($is_loose_equality) { - return $existing_var_type; - } - - return new Union([new TLiteralInt($value)]); - } - - if ($existing_var_atomic_type->as->hasInt()) { - $has_int = true; - } - } - } - - if ($has_int) { - $existing_int_types = $existing_var_type->getLiteralInts(); - - if ($existing_int_types) { - $can_be_equal = false; - $did_remove_type = false; - - foreach ($existing_var_atomic_types as $atomic_key => $atomic_type) { - if ($atomic_key !== $assertion - && !($atomic_type instanceof TPositiveInt && $value > 0) - && !($atomic_type instanceof TIntRange && $atomic_type->contains($value)) - ) { - $existing_var_type->removeType($atomic_key); - $did_remove_type = true; - } else { - $can_be_equal = true; - } - } - - if ($var_id - && $code_location - && (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1)) - ) { - self::triggerIssueForImpossible( - $existing_var_type, - $old_var_type_string, - $var_id, - $assertion, - $can_be_equal, - $negated, - $code_location, - $suppressed_issues - ); - } - } else { - $existing_var_type = new Union([new TLiteralInt($value)]); - } - } elseif ($var_id && $code_location && !$is_loose_equality) { - self::triggerIssueForImpossible( - $existing_var_type, - $old_var_type_string, - $var_id, - $assertion, - false, - $negated, - $code_location, - $suppressed_issues - ); - } elseif ($is_loose_equality && $existing_var_type->hasFloat()) { - // convert floats to ints - $existing_float_types = $existing_var_type->getLiteralFloats(); - - if ($existing_float_types) { - $can_be_equal = false; - $did_remove_type = false; - - foreach ($existing_var_atomic_types as $atomic_key => $_) { - if (strpos($atomic_key, 'float(') === 0) { - $atomic_key = 'int(' . substr($atomic_key, 6); - } - if ($atomic_key !== $assertion) { - $existing_var_type->removeType($atomic_key); - $did_remove_type = true; - } else { - $can_be_equal = true; - } - } - - if ($var_id - && $code_location - && (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1)) - ) { - self::triggerIssueForImpossible( - $existing_var_type, - $old_var_type_string, - $var_id, - $assertion, - $can_be_equal, - $negated, - $code_location, - $suppressed_issues - ); - } - } - } + return self::handleLiteralEqualityWithInt( + $statements_analyzer, + $assertion, + $bracket_pos, + $is_loose_equality, + $existing_var_type, + $old_var_type_string, + $var_id, + $negated, + $code_location, + $suppressed_issues + ); } elseif ($scalar_type === 'string' || $scalar_type === 'class-string' || $scalar_type === 'interface-string' @@ -1143,6 +1035,7 @@ private static function handleLiteralEquality( $existing_var_atomic_type = clone $existing_var_atomic_type; $existing_var_atomic_type->as = self::handleLiteralEquality( + $statements_analyzer, $assertion, $bracket_pos, false, @@ -1359,6 +1252,175 @@ private static function handleLiteralEquality( return $existing_var_type; } + private static function handleLiteralEqualityWithInt( + StatementsAnalyzer $statements_analyzer, + string $assertion, + int $bracket_pos, + bool $is_loose_equality, + Union $existing_var_type, + string $old_var_type_string, + ?string $var_id, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues + ): Union { + $value = (int) substr($assertion, $bracket_pos + 1, -1); + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + $compatible_int_type = self::getCompatibleIntType($existing_var_type, $value, $is_loose_equality); + if ($compatible_int_type !== null) { + return $compatible_int_type; + } + + $has_int = false; + + foreach ($existing_var_atomic_types as $existing_var_atomic_type) { + if ($existing_var_atomic_type instanceof TInt) { + $has_int = true; + } elseif ($existing_var_atomic_type instanceof TTemplateParam) { + $compatible_int_type = self::getCompatibleIntType($existing_var_type, $value, $is_loose_equality); + if ($compatible_int_type !== null) { + return $compatible_int_type; + } + + if ($existing_var_atomic_type->as->hasInt()) { + $has_int = true; + } + } elseif ($existing_var_atomic_type instanceof TClassConstant) { + $expanded = TypeExpander::expandAtomic( + $statements_analyzer->getCodebase(), + $existing_var_atomic_type, + $existing_var_atomic_type->fq_classlike_name, + $existing_var_atomic_type->fq_classlike_name, + null, + true, + true + ); + + if ($expanded instanceof Atomic) { + if ($expanded instanceof TInt) { + $has_int = true; + } + } else { + foreach ($expanded as $expanded_type) { + if ($expanded_type instanceof TInt) { + $has_int = true; + } + } + } + } + } + + if ($has_int) { + $existing_int_types = $existing_var_type->getLiteralInts(); + + if ($existing_int_types) { + $can_be_equal = false; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $atomic_key => $atomic_type) { + if ($atomic_key !== $assertion + && !($atomic_type instanceof TPositiveInt && $value > 0) + && !($atomic_type instanceof TIntRange && $atomic_type->contains($value)) + ) { + $existing_var_type->removeType($atomic_key); + $did_remove_type = true; + } else { + $can_be_equal = true; + } + } + + if ($var_id + && $code_location + && (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1)) + ) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + $can_be_equal, + $negated, + $code_location, + $suppressed_issues + ); + } + } else { + $existing_var_type = new Union([new TLiteralInt($value)]); + } + } elseif ($var_id && $code_location && !$is_loose_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + false, + $negated, + $code_location, + $suppressed_issues + ); + } elseif ($is_loose_equality && $existing_var_type->hasFloat()) { + // convert floats to ints + $existing_float_types = $existing_var_type->getLiteralFloats(); + + if ($existing_float_types) { + $can_be_equal = false; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $atomic_key => $_) { + if (strpos($atomic_key, 'float(') === 0) { + $atomic_key = 'int(' . substr($atomic_key, 6); + } + if ($atomic_key !== $assertion) { + $existing_var_type->removeType($atomic_key); + $did_remove_type = true; + } else { + $can_be_equal = true; + } + } + + if ($var_id + && $code_location + && (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1)) + ) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + $can_be_equal, + $negated, + $code_location, + $suppressed_issues + ); + } + } + } + + return $existing_var_type; + } + + private static function getCompatibleIntType( + Union $existing_var_type, + int $value, + bool $is_loose_equality + ): ?Union { + if ($existing_var_type->hasMixed() + || $existing_var_type->hasScalar() + || $existing_var_type->hasNumeric() + || $existing_var_type->hasArrayKey() + ) { + if ($is_loose_equality) { + return $existing_var_type; + } + + return new Union([new TLiteralInt($value)]); + } + + return null; + } + /** * @param array> $template_type_map * @param array $suppressed_issues From e3bb251757d8a28c4356fd9e5a3b33d2d1181495 Mon Sep 17 00:00:00 2001 From: orklah Date: Mon, 3 Jan 2022 23:45:23 +0100 Subject: [PATCH 2/3] handle cases where the classconst contains a compatible int type --- .../Internal/Type/AssertionReconciler.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 9cbabb6d257..a8a4073126f 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1266,15 +1266,13 @@ private static function handleLiteralEqualityWithInt( ): Union { $value = (int) substr($assertion, $bracket_pos + 1, -1); - $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); - $compatible_int_type = self::getCompatibleIntType($existing_var_type, $value, $is_loose_equality); if ($compatible_int_type !== null) { return $compatible_int_type; } $has_int = false; - + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); foreach ($existing_var_atomic_types as $existing_var_atomic_type) { if ($existing_var_atomic_type instanceof TInt) { $has_int = true; @@ -1299,11 +1297,26 @@ private static function handleLiteralEqualityWithInt( ); if ($expanded instanceof Atomic) { + $compatible_int_type = self::getCompatibleIntType($existing_var_type, $value, $is_loose_equality); + if ($compatible_int_type !== null) { + return $compatible_int_type; + } + if ($expanded instanceof TInt) { $has_int = true; } } else { foreach ($expanded as $expanded_type) { + $compatible_int_type = self::getCompatibleIntType( + $existing_var_type, + $value, + $is_loose_equality + ); + + if ($compatible_int_type !== null) { + return $compatible_int_type; + } + if ($expanded_type instanceof TInt) { $has_int = true; } From a53b5215b88cd57e53bb2acb2038dd3230c96f8c Mon Sep 17 00:00:00 2001 From: orklah Date: Mon, 3 Jan 2022 23:52:48 +0100 Subject: [PATCH 3/3] add missing phpdoc --- src/Psalm/Internal/Type/AssertionReconciler.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index a8a4073126f..e7e6478c600 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1252,6 +1252,9 @@ private static function handleLiteralEquality( return $existing_var_type; } + /** + * @param string[] $suppressed_issues + */ private static function handleLiteralEqualityWithInt( StatementsAnalyzer $statements_analyzer, string $assertion,