From a8c2fcde76d5ae06b28b5650f91fd6c8f45e52ff Mon Sep 17 00:00:00 2001 From: orklah Date: Tue, 4 Jan 2022 01:28:46 +0100 Subject: [PATCH 01/19] resolve ClassConst before checking equalities and fix things --- .../Internal/Type/AssertionReconciler.php | 176 +++++++++--------- 1 file changed, 90 insertions(+), 86 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index e7e6478c600..d99f6e6c09b 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\TArrayKey; use Psalm\Type\Atomic\TClassConstant; use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TEnumCase; @@ -36,7 +37,9 @@ use Psalm\Type\Atomic\TLiteralString; use Psalm\Type\Atomic\TMixed; use Psalm\Type\Atomic\TNamedObject; +use Psalm\Type\Atomic\TNumeric; use Psalm\Type\Atomic\TPositiveInt; +use Psalm\Type\Atomic\TScalar; use Psalm\Type\Atomic\TString; use Psalm\Type\Atomic\TTemplateParam; use Psalm\Type\Reconciler; @@ -44,6 +47,7 @@ use function array_intersect_key; use function array_merge; +use function array_push; use function count; use function explode; use function get_class; @@ -978,15 +982,37 @@ private static function handleLiteralEquality( $scalar_type = substr($assertion, 0, $bracket_pos); - $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + $existing_var_atomic_types = []; + + foreach ($existing_var_type->getAtomicTypes() as $existing_var_atomic_type) { + if ($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) { + $existing_var_atomic_types[] = $expanded; + } else { + array_push($existing_var_atomic_types, ...$expanded); + } + } else { + $existing_var_atomic_types[] = $existing_var_atomic_type; + } + } if ($scalar_type === 'int') { return self::handleLiteralEqualityWithInt( - $statements_analyzer, $assertion, $bracket_pos, $is_loose_equality, $existing_var_type, + $existing_var_atomic_types, $old_var_type_string, $var_id, $negated, @@ -1253,14 +1279,15 @@ private static function handleLiteralEquality( } /** - * @param string[] $suppressed_issues + * @param list $existing_var_atomic_types + * @param string[] $suppressed_issues */ private static function handleLiteralEqualityWithInt( - StatementsAnalyzer $statements_analyzer, string $assertion, int $bracket_pos, bool $is_loose_equality, Union $existing_var_type, + array $existing_var_atomic_types, string $old_var_type_string, ?string $var_id, bool $negated, @@ -1269,18 +1296,32 @@ private static function handleLiteralEqualityWithInt( ): Union { $value = (int) substr($assertion, $bracket_pos + 1, -1); - $compatible_int_type = self::getCompatibleIntType($existing_var_type, $value, $is_loose_equality); + $compatible_int_type = self::getCompatibleIntType( + $existing_var_type, + $existing_var_atomic_types, + $value, + $is_loose_equality + ); + if ($compatible_int_type !== null) { return $compatible_int_type; } $has_int = false; - $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + $literal_ints = []; foreach ($existing_var_atomic_types as $existing_var_atomic_type) { - if ($existing_var_atomic_type instanceof TInt) { + if ($existing_var_atomic_type instanceof TLiteralInt) { + $has_int = true; + $literal_ints[] = $existing_var_atomic_type->value; + } elseif ($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); + $compatible_int_type = self::getCompatibleIntType( + $existing_var_type, + $existing_var_atomic_type->as->getAtomicTypes(), + $value, + $is_loose_equality + ); if ($compatible_int_type !== null) { return $compatible_int_type; } @@ -1288,82 +1329,39 @@ private static function handleLiteralEqualityWithInt( 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) { - $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; - } - } - } } } 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; - } - } + $can_be_equal = false; + $did_remove_type = false; - if ($var_id - && $code_location - && (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1)) + foreach ($existing_var_atomic_types as $atomic_key => $atomic_type) { + if ($atomic_key !== $value + && !($atomic_type instanceof TPositiveInt && $value > 0) + && !($atomic_type instanceof TIntRange && $atomic_type->contains($value)) ) { - self::triggerIssueForImpossible( - $existing_var_type, - $old_var_type_string, - $var_id, - $assertion, - $can_be_equal, - $negated, - $code_location, - $suppressed_issues - ); + $existing_var_type->removeType($atomic_key); + $did_remove_type = true; + } else { + $can_be_equal = true; } - } else { - $existing_var_type = new Union([new TLiteralInt($value)]); + } + + 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 + ); } } elseif ($var_id && $code_location && !$is_loose_equality) { self::triggerIssueForImpossible( @@ -1417,21 +1415,27 @@ private static function handleLiteralEqualityWithInt( return $existing_var_type; } + /** + * @param list $existing_var_atomic_types + */ private static function getCompatibleIntType( Union $existing_var_type, + array $existing_var_atomic_types, 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; - } + foreach ($existing_var_atomic_types as $existing_var_atomic_type) { + if ($existing_var_atomic_type instanceof TMixed + || $existing_var_atomic_type instanceof TScalar + || $existing_var_atomic_type instanceof TNumeric + || $existing_var_atomic_type instanceof TArrayKey + ) { + if ($is_loose_equality) { + return $existing_var_type; + } - return new Union([new TLiteralInt($value)]); + return new Union([new TLiteralInt($value)]); + } } return null; From 2ada8ee8b05b624f6077e43d423be3d7a230f13d Mon Sep 17 00:00:00 2001 From: orklah Date: Tue, 4 Jan 2022 20:31:07 +0100 Subject: [PATCH 02/19] refactor literal int equality check --- .../Internal/Type/AssertionReconciler.php | 128 ++++++++---------- 1 file changed, 53 insertions(+), 75 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index d99f6e6c09b..7683e58e0c7 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1307,15 +1307,41 @@ private static function handleLiteralEqualityWithInt( return $compatible_int_type; } - $has_int = false; - $literal_ints = []; foreach ($existing_var_atomic_types as $existing_var_atomic_type) { - if ($existing_var_atomic_type instanceof TLiteralInt) { - $has_int = true; - $literal_ints[] = $existing_var_atomic_type->value; - } elseif ($existing_var_atomic_type instanceof TInt) { - $has_int = true; - } elseif ($existing_var_atomic_type instanceof TTemplateParam) { + if ($existing_var_atomic_type instanceof TPositiveInt && $value > 0) { + return new Union([new TLiteralInt($value)]); + } + + if ($existing_var_atomic_type instanceof TIntRange && $existing_var_atomic_type->contains($value)) { + return new Union([new TLiteralInt($value)]); + } + + if ($existing_var_atomic_type instanceof TLiteralInt && $existing_var_atomic_type->value === $value) { + //if we're here, we check that we add at least another type in the union, otherwise it's redundant + + if ($existing_var_type->isSingleIntLiteral()) { + if ($var_id && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + true, + $negated, + $code_location, + $suppressed_issues + ); + } + return $existing_var_type; + } + return new Union([new TLiteralInt($value)]); + } + + if ($existing_var_atomic_type instanceof TInt) { + return new Union([new TLiteralInt($value)]); + } + + if ($existing_var_atomic_type instanceof TTemplateParam) { $compatible_int_type = self::getCompatibleIntType( $existing_var_type, $existing_var_atomic_type->as->getAtomicTypes(), @@ -1327,43 +1353,31 @@ private static function handleLiteralEqualityWithInt( } if ($existing_var_atomic_type->as->hasInt()) { - $has_int = true; + return new Union([new TLiteralInt($value)]); } } - } - if ($has_int) { - $can_be_equal = false; - $did_remove_type = false; + if ($is_loose_equality + && $existing_var_atomic_type instanceof TLiteralFloat + && (int)$existing_var_atomic_type->value === $value + ) { + return new Union([$existing_var_atomic_type]); + } - foreach ($existing_var_atomic_types as $atomic_key => $atomic_type) { - if ($atomic_key !== $value - && !($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; + //here we'll accept any type that *could* possibly be valid on loose equality and return the original type + if ($is_loose_equality) { + if ($existing_var_atomic_type instanceof TString) { + return $existing_var_type; } - } - 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 - ); + if ($existing_var_atomic_type instanceof TFloat) { + return $existing_var_type; + } } - } elseif ($var_id && $code_location && !$is_loose_equality) { + } + + //if we're here, no type was eligible for the given literal. We'll emit an impossible error for this assertion + if ($var_id && $code_location) { self::triggerIssueForImpossible( $existing_var_type, $old_var_type_string, @@ -1374,45 +1388,9 @@ private static function handleLiteralEqualityWithInt( $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; + return Type::getNever(); } /** From 36349a13705b9967835f1291540abeec74ba848b Mon Sep 17 00:00:00 2001 From: orklah Date: Tue, 4 Jan 2022 21:10:39 +0100 Subject: [PATCH 03/19] refactor literal string equality check --- .../Internal/Type/AssertionReconciler.php | 289 +++++++++++------- 1 file changed, 179 insertions(+), 110 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 7683e58e0c7..28e8bd1e8ae 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1025,116 +1025,20 @@ private static function handleLiteralEquality( || $scalar_type === 'callable-string' || $scalar_type === 'trait-string' ) { - if ($existing_var_type->hasMixed() - || $existing_var_type->hasScalar() - || $existing_var_type->hasArrayKey() - ) { - if ($is_loose_equality) { - return $existing_var_type; - } - - if ($scalar_type === 'class-string' - || $scalar_type === 'interface-string' - || $scalar_type === 'trait-string' - ) { - return new Union([new TLiteralClassString($value)]); - } - - return new Union([new TLiteralString($value)]); - } - - $has_string = false; - - foreach ($existing_var_atomic_types as $existing_var_atomic_type) { - if ($existing_var_atomic_type instanceof TString) { - $has_string = true; - } elseif ($existing_var_atomic_type instanceof TTemplateParam) { - if ($existing_var_atomic_type->as->hasMixed() - || $existing_var_atomic_type->as->hasString() - || $existing_var_atomic_type->as->hasScalar() - || $existing_var_atomic_type->as->hasArrayKey() - ) { - if ($is_loose_equality) { - return $existing_var_type; - } - - $existing_var_atomic_type = clone $existing_var_atomic_type; - - $existing_var_atomic_type->as = self::handleLiteralEquality( - $statements_analyzer, - $assertion, - $bracket_pos, - false, - $existing_var_atomic_type->as, - $old_var_type_string, - $var_id, - $negated, - $code_location, - $suppressed_issues - ); - - return new Union([$existing_var_atomic_type]); - } - - if ($existing_var_atomic_type->as->hasString()) { - $has_string = true; - } - } - } - - if ($has_string) { - $existing_string_types = $existing_var_type->getLiteralStrings(); - - if ($existing_string_types) { - $can_be_equal = false; - $did_remove_type = false; - - foreach ($existing_var_atomic_types as $atomic_key => $_) { - 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 - ); - } - } else { - if ($scalar_type === 'class-string' - || $scalar_type === 'interface-string' - || $scalar_type === 'trait-string' - ) { - $existing_var_type = new Union([new TLiteralClassString($value)]); - } else { - $existing_var_type = new Union([new TLiteralString($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 - ); - } + return self::handleLiteralEqualityWithString( + $statements_analyzer, + $assertion, + $scalar_type, + $bracket_pos, + $is_loose_equality, + $existing_var_type, + $existing_var_atomic_types, + $old_var_type_string, + $var_id, + $negated, + $code_location, + $suppressed_issues + ); } elseif ($scalar_type === 'float') { $value = (float) $value; @@ -1393,6 +1297,138 @@ private static function handleLiteralEqualityWithInt( return Type::getNever(); } + /** + * @param list $existing_var_atomic_types + * @param string[] $suppressed_issues + */ + private static function handleLiteralEqualityWithString( + StatementsAnalyzer $statements_analyzer, + string $assertion, + string $scalar_type, + int $bracket_pos, + bool $is_loose_equality, + Union $existing_var_type, + array $existing_var_atomic_types, + string $old_var_type_string, + ?string $var_id, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues + ): Union { + $value = substr($assertion, $bracket_pos + 1, -1); + + $compatible_string_type = self::getCompatibleStringType( + $existing_var_type, + $existing_var_atomic_types, + $value, + $scalar_type, + $is_loose_equality + ); + + if ($compatible_string_type !== null) { + return $compatible_string_type; + } + + foreach ($existing_var_atomic_types as $existing_var_atomic_type) { + if ($existing_var_atomic_type instanceof TLiteralString && $existing_var_atomic_type->value === $value) { + //if we're here, we check that we add at least another type in the union, otherwise it's redundant + + if ($existing_var_type->isSingleStringLiteral()) { + if ($var_id && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + true, + $negated, + $code_location, + $suppressed_issues + ); + } + return $existing_var_type; + } + + if ($scalar_type === 'class-string' + || $scalar_type === 'interface-string' + || $scalar_type === 'trait-string' + ) { + return new Union([new TLiteralClassString($value)]); + } + + return new Union([new TLiteralString($value)]); + } + + if ($existing_var_atomic_type instanceof TString) { + if ($scalar_type === 'class-string' + || $scalar_type === 'interface-string' + || $scalar_type === 'trait-string' + ) { + return new Union([new TLiteralClassString($value)]); + } + + return new Union([new TLiteralString($value)]); + } + + if ($existing_var_atomic_type instanceof TTemplateParam) { + $compatible_string_type = self::getCompatibleStringType( + $existing_var_type, + $existing_var_atomic_type->as->getAtomicTypes(), + $value, + $scalar_type, + $is_loose_equality + ); + if ($compatible_string_type !== null) { + return $compatible_string_type; + } + + if ($existing_var_atomic_type->as->hasString()) { + if ($scalar_type === 'class-string' + || $scalar_type === 'interface-string' + || $scalar_type === 'trait-string' + ) { + return new Union([new TLiteralClassString($value)]); + } + + return new Union([new TLiteralString($value)]); + } + + $existing_var_atomic_type = clone $existing_var_atomic_type; + + $existing_var_atomic_type->as = self::handleLiteralEquality( + $statements_analyzer, + $assertion, + $bracket_pos, + false, + $existing_var_atomic_type->as, + $old_var_type_string, + $var_id, + $negated, + $code_location, + $suppressed_issues + ); + + return new Union([$existing_var_atomic_type]); + } + } + + //if we're here, no type was eligible for the given literal. We'll emit an impossible error for this assertion + if ($var_id && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + false, + $negated, + $code_location, + $suppressed_issues + ); + } + + return Type::getNever(); + } + /** * @param list $existing_var_atomic_types */ @@ -1419,6 +1455,39 @@ private static function getCompatibleIntType( return null; } + /** + * @param list $existing_var_atomic_types + */ + private static function getCompatibleStringType( + Union $existing_var_type, + array $existing_var_atomic_types, + string $value, + string $scalar_type, + bool $is_loose_equality + ): ?Union { + foreach ($existing_var_atomic_types as $existing_var_atomic_type) { + if ($existing_var_atomic_type instanceof TMixed + || $existing_var_atomic_type instanceof TScalar + || $existing_var_atomic_type instanceof TArrayKey + ) { + if ($is_loose_equality) { + return $existing_var_type; + } + + if ($scalar_type === 'class-string' + || $scalar_type === 'interface-string' + || $scalar_type === 'trait-string' + ) { + return new Union([new TLiteralClassString($value)]); + } + + return new Union([new TLiteralString($value)]); + } + } + + return null; + } + /** * @param array> $template_type_map * @param array $suppressed_issues From 950710a47a9797dc4c20ad0aae96f13205e5bd20 Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 09:58:56 +0100 Subject: [PATCH 04/19] Fixup!!! make sure to add atomics with the correct offset key --- src/Psalm/Internal/Type/AssertionReconciler.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 28e8bd1e8ae..416ba808695 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -997,12 +997,14 @@ private static function handleLiteralEquality( ); if ($expanded instanceof Atomic) { - $existing_var_atomic_types[] = $expanded; + $existing_var_atomic_types[$expanded->getKey()] = $expanded; } else { - array_push($existing_var_atomic_types, ...$expanded); + foreach ($expanded as $atomic_type) { + $existing_var_atomic_types[$atomic_type->getKey()] = $atomic_type; + } } } else { - $existing_var_atomic_types[] = $existing_var_atomic_type; + $existing_var_atomic_types[$existing_var_atomic_type->getKey()] = $existing_var_atomic_type; } } From 3fc813920535a427eddc9806ae09d2781077cc0f Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 11:17:39 +0100 Subject: [PATCH 05/19] Fixup!!! only allow general TInt to pass --- src/Psalm/Internal/Type/AssertionReconciler.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 416ba808695..f0b7d845436 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1185,7 +1185,7 @@ private static function handleLiteralEquality( } /** - * @param list $existing_var_atomic_types + * @param array $existing_var_atomic_types * @param string[] $suppressed_issues */ private static function handleLiteralEqualityWithInt( @@ -1243,7 +1243,7 @@ private static function handleLiteralEqualityWithInt( return new Union([new TLiteralInt($value)]); } - if ($existing_var_atomic_type instanceof TInt) { + if (get_class($existing_var_atomic_type) === TInt::class) { return new Union([new TLiteralInt($value)]); } @@ -1300,7 +1300,7 @@ private static function handleLiteralEqualityWithInt( } /** - * @param list $existing_var_atomic_types + * @param array $existing_var_atomic_types * @param string[] $suppressed_issues */ private static function handleLiteralEqualityWithString( @@ -1432,7 +1432,7 @@ private static function handleLiteralEqualityWithString( } /** - * @param list $existing_var_atomic_types + * @param array $existing_var_atomic_types */ private static function getCompatibleIntType( Union $existing_var_type, @@ -1458,7 +1458,7 @@ private static function getCompatibleIntType( } /** - * @param list $existing_var_atomic_types + * @param array $existing_var_atomic_types */ private static function getCompatibleStringType( Union $existing_var_type, From 5c0fc46734546cb557f98b6d4e966068cf9f344d Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 12:10:32 +0100 Subject: [PATCH 06/19] Fixup!!! be more flexible on loose_equality for strings --- src/Psalm/Internal/Type/AssertionReconciler.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index f0b7d845436..e94a041a6da 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1412,6 +1412,17 @@ private static function handleLiteralEqualityWithString( return new Union([$existing_var_atomic_type]); } + + //here we'll accept any type that *could* possibly be valid on loose equality and return the original type + if ($is_loose_equality) { + if ($existing_var_atomic_type instanceof TInt) { + return $existing_var_type; + } + + if ($existing_var_atomic_type instanceof TFloat) { + return $existing_var_type; + } + } } //if we're here, no type was eligible for the given literal. We'll emit an impossible error for this assertion From 83a02d9a619a96beec19c8163338307a11103cd4 Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 12:21:50 +0100 Subject: [PATCH 07/19] Fixup!!! don't allow checks on literals --- src/Psalm/Internal/Type/AssertionReconciler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index e94a041a6da..97c8662a04a 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1361,7 +1361,7 @@ private static function handleLiteralEqualityWithString( return new Union([new TLiteralString($value)]); } - if ($existing_var_atomic_type instanceof TString) { + if ($existing_var_atomic_type instanceof TString && !$existing_var_atomic_type instanceof TLiteralString) { if ($scalar_type === 'class-string' || $scalar_type === 'interface-string' || $scalar_type === 'trait-string' From 411399b600034c8a198364efe28d07f2d47809b1 Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 12:22:44 +0100 Subject: [PATCH 08/19] Fixup!!! don't allow checks on literals --- src/Psalm/Internal/Type/AssertionReconciler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 97c8662a04a..31dfc8430a0 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1243,7 +1243,7 @@ private static function handleLiteralEqualityWithInt( return new Union([new TLiteralInt($value)]); } - if (get_class($existing_var_atomic_type) === TInt::class) { + if ($existing_var_atomic_type instanceof TInt && !$existing_var_atomic_type instanceof TLiteralInt) { return new Union([new TLiteralInt($value)]); } From 16bb62b4990fd9ae71fb96f78f9d4aa9f6646eb9 Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 12:41:08 +0100 Subject: [PATCH 09/19] Fixup!!! don't allow checks on literals --- .../Internal/Type/AssertionReconciler.php | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 31dfc8430a0..64d2f283714 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1270,13 +1270,17 @@ private static function handleLiteralEqualityWithInt( return new Union([$existing_var_atomic_type]); } - //here we'll accept any type that *could* possibly be valid on loose equality and return the original type + //here we'll accept non-literal type that *could* match on loose equality and return the original type if ($is_loose_equality) { - if ($existing_var_atomic_type instanceof TString) { + if ($existing_var_atomic_type instanceof TString + && !$existing_var_atomic_type instanceof TLiteralString + ) { return $existing_var_type; } - if ($existing_var_atomic_type instanceof TFloat) { + if ($existing_var_atomic_type instanceof TFloat + && !$existing_var_atomic_type instanceof TLiteralFloat + ) { return $existing_var_type; } } @@ -1413,13 +1417,17 @@ private static function handleLiteralEqualityWithString( return new Union([$existing_var_atomic_type]); } - //here we'll accept any type that *could* possibly be valid on loose equality and return the original type + //here we'll accept non-literal type that *could* match on loose equality and return the original type if ($is_loose_equality) { - if ($existing_var_atomic_type instanceof TInt) { + if ($existing_var_atomic_type instanceof TInt + && !$existing_var_atomic_type instanceof TLiteralInt + ) { return $existing_var_type; } - if ($existing_var_atomic_type instanceof TFloat) { + if ($existing_var_atomic_type instanceof TFloat + && !$existing_var_atomic_type instanceof TLiteralFloat + ) { return $existing_var_type; } } From 8099126958f2f273ee5d170733b5ea1b27028c0f Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 17:52:04 +0100 Subject: [PATCH 10/19] make sure to keep the origin of the type (from_docblock) --- .../Internal/Type/AssertionReconciler.php | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 64d2f283714..fc1dbca9146 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1202,6 +1202,10 @@ private static function handleLiteralEqualityWithInt( ): Union { $value = (int) substr($assertion, $bracket_pos + 1, -1); + // we create the literal that is being asserted. We'll return this when we're sure this is the resulting type + $literal_asserted_type = new Union([new TLiteralInt($value)]); + $literal_asserted_type->from_docblock = $existing_var_type->from_docblock; + $compatible_int_type = self::getCompatibleIntType( $existing_var_type, $existing_var_atomic_types, @@ -1215,11 +1219,11 @@ private static function handleLiteralEqualityWithInt( foreach ($existing_var_atomic_types as $existing_var_atomic_type) { if ($existing_var_atomic_type instanceof TPositiveInt && $value > 0) { - return new Union([new TLiteralInt($value)]); + return $literal_asserted_type; } if ($existing_var_atomic_type instanceof TIntRange && $existing_var_atomic_type->contains($value)) { - return new Union([new TLiteralInt($value)]); + return $literal_asserted_type; } if ($existing_var_atomic_type instanceof TLiteralInt && $existing_var_atomic_type->value === $value) { @@ -1240,11 +1244,11 @@ private static function handleLiteralEqualityWithInt( } return $existing_var_type; } - return new Union([new TLiteralInt($value)]); + return $literal_asserted_type; } if ($existing_var_atomic_type instanceof TInt && !$existing_var_atomic_type instanceof TLiteralInt) { - return new Union([new TLiteralInt($value)]); + return $literal_asserted_type; } if ($existing_var_atomic_type instanceof TTemplateParam) { @@ -1259,7 +1263,7 @@ private static function handleLiteralEqualityWithInt( } if ($existing_var_atomic_type->as->hasInt()) { - return new Union([new TLiteralInt($value)]); + return $literal_asserted_type; } } @@ -1323,6 +1327,12 @@ private static function handleLiteralEqualityWithString( ): Union { $value = substr($assertion, $bracket_pos + 1, -1); + // we create the literal that is being asserted. We'll return this when we're sure this is the resulting type + $literal_asserted_type_string = new Union([new TLiteralString($value)]); + $literal_asserted_type_string->from_docblock = $existing_var_type->from_docblock; + $literal_asserted_type_classstring = new Union([new TLiteralClassString($value)]); + $literal_asserted_type_classstring->from_docblock = $existing_var_type->from_docblock; + $compatible_string_type = self::getCompatibleStringType( $existing_var_type, $existing_var_atomic_types, @@ -1359,10 +1369,10 @@ private static function handleLiteralEqualityWithString( || $scalar_type === 'interface-string' || $scalar_type === 'trait-string' ) { - return new Union([new TLiteralClassString($value)]); + return $literal_asserted_type_classstring; } - return new Union([new TLiteralString($value)]); + return $literal_asserted_type_string; } if ($existing_var_atomic_type instanceof TString && !$existing_var_atomic_type instanceof TLiteralString) { @@ -1370,10 +1380,10 @@ private static function handleLiteralEqualityWithString( || $scalar_type === 'interface-string' || $scalar_type === 'trait-string' ) { - return new Union([new TLiteralClassString($value)]); + return $literal_asserted_type_classstring; } - return new Union([new TLiteralString($value)]); + return $literal_asserted_type_string; } if ($existing_var_atomic_type instanceof TTemplateParam) { @@ -1393,10 +1403,10 @@ private static function handleLiteralEqualityWithString( || $scalar_type === 'interface-string' || $scalar_type === 'trait-string' ) { - return new Union([new TLiteralClassString($value)]); + return $literal_asserted_type_classstring; } - return new Union([new TLiteralString($value)]); + return $literal_asserted_type_string; } $existing_var_atomic_type = clone $existing_var_atomic_type; @@ -1469,7 +1479,9 @@ private static function getCompatibleIntType( return $existing_var_type; } - return new Union([new TLiteralInt($value)]); + $asserted_type = new Union([new TLiteralInt($value)]); + $asserted_type->from_docblock = $existing_var_type->from_docblock; + return $asserted_type; } } @@ -1499,10 +1511,14 @@ private static function getCompatibleStringType( || $scalar_type === 'interface-string' || $scalar_type === 'trait-string' ) { - return new Union([new TLiteralClassString($value)]); + $asserted_type = new Union([new TLiteralClassString($value)]); + $asserted_type->from_docblock = $existing_var_type->from_docblock; + return $asserted_type; } - return new Union([new TLiteralString($value)]); + $asserted_type = new Union([new TLiteralString($value)]); + $asserted_type->from_docblock = $existing_var_type->from_docblock; + return $asserted_type; } } From c1575d29fdb43672430fec2d029cfc246c844830 Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 18:06:08 +0100 Subject: [PATCH 11/19] consistency with string handling --- .../Internal/Type/AssertionReconciler.php | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index fc1dbca9146..0b38773c0fb 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1010,6 +1010,7 @@ private static function handleLiteralEquality( if ($scalar_type === 'int') { return self::handleLiteralEqualityWithInt( + $statements_analyzer, $assertion, $bracket_pos, $is_loose_equality, @@ -1189,6 +1190,7 @@ private static function handleLiteralEquality( * @param string[] $suppressed_issues */ private static function handleLiteralEqualityWithInt( + StatementsAnalyzer $statements_analyzer, string $assertion, int $bracket_pos, bool $is_loose_equality, @@ -1262,9 +1264,22 @@ private static function handleLiteralEqualityWithInt( return $compatible_int_type; } - if ($existing_var_atomic_type->as->hasInt()) { - return $literal_asserted_type; - } + $existing_var_atomic_type = clone $existing_var_atomic_type; + + $existing_var_atomic_type->as = self::handleLiteralEquality( + $statements_analyzer, + $assertion, + $bracket_pos, + false, + $existing_var_atomic_type->as, + $old_var_type_string, + $var_id, + $negated, + $code_location, + $suppressed_issues + ); + + return new Union([$existing_var_atomic_type]); } if ($is_loose_equality From e0fb87596d0451d392decefea998770f4e1fd64d Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 18:21:38 +0100 Subject: [PATCH 12/19] typo --- src/Psalm/Internal/Type/AssertionReconciler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 0b38773c0fb..5861de1d52e 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1229,7 +1229,7 @@ private static function handleLiteralEqualityWithInt( } if ($existing_var_atomic_type instanceof TLiteralInt && $existing_var_atomic_type->value === $value) { - //if we're here, we check that we add at least another type in the union, otherwise it's redundant + //if we're here, we check that we had at least another type in the union, otherwise it's redundant if ($existing_var_type->isSingleIntLiteral()) { if ($var_id && $code_location) { @@ -1362,7 +1362,7 @@ private static function handleLiteralEqualityWithString( foreach ($existing_var_atomic_types as $existing_var_atomic_type) { if ($existing_var_atomic_type instanceof TLiteralString && $existing_var_atomic_type->value === $value) { - //if we're here, we check that we add at least another type in the union, otherwise it's redundant + //if we're here, we check that we had at least another type in the union, otherwise it's redundant if ($existing_var_type->isSingleStringLiteral()) { if ($var_id && $code_location) { From 7dccfa4ff901e7454776b9640c9688b33dfd398c Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 18:44:58 +0100 Subject: [PATCH 13/19] CS --- src/Psalm/Internal/Type/AssertionReconciler.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 5861de1d52e..4eb8baea308 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -47,7 +47,6 @@ use function array_intersect_key; use function array_merge; -use function array_push; use function count; use function explode; use function get_class; From 26542afdf4374e86bef5f92e492caf103ff30fbc Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 19:45:22 +0100 Subject: [PATCH 14/19] accept more loose literal equalities and loop over non-literal types on a separate loop --- .../Internal/Type/AssertionReconciler.php | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 4eb8baea308..523966412da 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1288,6 +1288,16 @@ private static function handleLiteralEqualityWithInt( return new Union([$existing_var_atomic_type]); } + if ($is_loose_equality + && $existing_var_atomic_type instanceof TLiteralString + && (int)$existing_var_atomic_type->value === $value + ) { + return new Union([$existing_var_atomic_type]); + } + } + + //here we'll accept non-literal type that *could* match on loose equality and return the original type + foreach ($existing_var_atomic_types as $existing_var_atomic_type) { //here we'll accept non-literal type that *could* match on loose equality and return the original type if ($is_loose_equality) { if ($existing_var_atomic_type instanceof TString @@ -1441,6 +1451,23 @@ private static function handleLiteralEqualityWithString( return new Union([$existing_var_atomic_type]); } + if ($is_loose_equality + && $existing_var_atomic_type instanceof TLiteralInt + && (string)$existing_var_atomic_type->value === $value + ) { + return new Union([$existing_var_atomic_type]); + } + + if ($is_loose_equality + && $existing_var_atomic_type instanceof TLiteralFloat + && (string)$existing_var_atomic_type->value === $value + ) { + return new Union([$existing_var_atomic_type]); + } + } + + //here we'll accept non-literal type that *could* match on loose equality and return the original type + foreach ($existing_var_atomic_types as $existing_var_atomic_type) { //here we'll accept non-literal type that *could* match on loose equality and return the original type if ($is_loose_equality) { if ($existing_var_atomic_type instanceof TInt From fac7317af3efb6d143aa719bf7dbdc85e7098142 Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 20:00:56 +0100 Subject: [PATCH 15/19] refactor float as well --- .../Internal/Type/AssertionReconciler.php | 277 ++++++++++++------ 1 file changed, 185 insertions(+), 92 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 523966412da..0787fe77def 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1042,98 +1042,20 @@ private static function handleLiteralEquality( $suppressed_issues ); } elseif ($scalar_type === 'float') { - $value = (float) $value; - - if ($existing_var_type->hasMixed() || $existing_var_type->hasScalar() || $existing_var_type->hasNumeric()) { - if ($is_loose_equality) { - return $existing_var_type; - } - - return new Union([new TLiteralFloat($value)]); - } - - if ($existing_var_type->hasFloat()) { - $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 ($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 - ); - } - } else { - $existing_var_type = new Union([new TLiteralFloat($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->hasInt()) { - // convert ints to floats - $existing_float_types = $existing_var_type->getLiteralInts(); - - if ($existing_float_types) { - $can_be_equal = false; - $did_remove_type = false; - - foreach ($existing_var_atomic_types as $atomic_key => $_) { - if (strpos($atomic_key, 'int(') === 0) { - $atomic_key = 'float(' . substr($atomic_key, 4); - } - 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::handleLiteralEqualityWithFloat( + $statements_analyzer, + $assertion, + $scalar_type, + $bracket_pos, + $is_loose_equality, + $existing_var_type, + $existing_var_atomic_types, + $old_var_type_string, + $var_id, + $negated, + $code_location, + $suppressed_issues + ); } elseif ($scalar_type === 'enum') { [$fq_enum_name, $case_name] = explode('::', $value); @@ -1501,6 +1423,150 @@ private static function handleLiteralEqualityWithString( return Type::getNever(); } + /** + * @param array $existing_var_atomic_types + * @param string[] $suppressed_issues + */ + private static function handleLiteralEqualityWithFloat( + StatementsAnalyzer $statements_analyzer, + string $assertion, + string $scalar_type, + int $bracket_pos, + bool $is_loose_equality, + Union $existing_var_type, + array $existing_var_atomic_types, + string $old_var_type_string, + ?string $var_id, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues + ): Union { + $value = (float)substr($assertion, $bracket_pos + 1, -1); + + // we create the literal that is being asserted. We'll return this when we're sure this is the resulting type + $literal_asserted_type = new Union([new TLiteralFloat($value)]); + $literal_asserted_type->from_docblock = $existing_var_type->from_docblock; + + $compatible_float_type = self::getCompatibleFloatType( + $existing_var_type, + $existing_var_atomic_types, + $value, + $is_loose_equality + ); + + if ($compatible_float_type !== null) { + return $compatible_float_type; + } + + foreach ($existing_var_atomic_types as $existing_var_atomic_type) { + if ($existing_var_atomic_type instanceof TLiteralFloat && $existing_var_atomic_type->value === $value) { + //if we're here, we check that we had at least another type in the union, otherwise it's redundant + + if ($existing_var_type->isSingleFloatLiteral()) { + if ($var_id && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + true, + $negated, + $code_location, + $suppressed_issues + ); + } + return $existing_var_type; + } + + return $literal_asserted_type; + } + + if ($existing_var_atomic_type instanceof TFloat && !$existing_var_atomic_type instanceof TLiteralFloat) { + return $literal_asserted_type; + } + + if ($existing_var_atomic_type instanceof TTemplateParam) { + $compatible_float_type = self::getCompatibleFloatType( + $existing_var_type, + $existing_var_atomic_type->as->getAtomicTypes(), + $value, + $is_loose_equality + ); + if ($compatible_float_type !== null) { + return $compatible_float_type; + } + + if ($existing_var_atomic_type->as->hasFloat()) { + return $literal_asserted_type; + } + + $existing_var_atomic_type = clone $existing_var_atomic_type; + + $existing_var_atomic_type->as = self::handleLiteralEquality( + $statements_analyzer, + $assertion, + $bracket_pos, + false, + $existing_var_atomic_type->as, + $old_var_type_string, + $var_id, + $negated, + $code_location, + $suppressed_issues + ); + + return new Union([$existing_var_atomic_type]); + } + + if ($is_loose_equality + && $existing_var_atomic_type instanceof TLiteralInt + && (float)$existing_var_atomic_type->value === $value + ) { + return new Union([$existing_var_atomic_type]); + } + + if ($is_loose_equality + && $existing_var_atomic_type instanceof TLiteralString + && (float)$existing_var_atomic_type->value === $value + ) { + return new Union([$existing_var_atomic_type]); + } + } + + //here we'll accept non-literal type that *could* match on loose equality and return the original type + foreach ($existing_var_atomic_types as $existing_var_atomic_type) { + if ($is_loose_equality) { + if ($existing_var_atomic_type instanceof TInt + && !$existing_var_atomic_type instanceof TLiteralInt + ) { + return $existing_var_type; + } + + if ($existing_var_atomic_type instanceof TFloat + && !$existing_var_atomic_type instanceof TLiteralFloat + ) { + return $existing_var_type; + } + } + } + + //if we're here, no type was eligible for the given literal. We'll emit an impossible error for this assertion + if ($var_id && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + false, + $negated, + $code_location, + $suppressed_issues + ); + } + + return Type::getNever(); + } + /** * @param array $existing_var_atomic_types */ @@ -1566,6 +1632,33 @@ private static function getCompatibleStringType( return null; } + /** + * @param array $existing_var_atomic_types + */ + private static function getCompatibleFloatType( + Union $existing_var_type, + array $existing_var_atomic_types, + int $value, + bool $is_loose_equality + ): ?Union { + foreach ($existing_var_atomic_types as $existing_var_atomic_type) { + if ($existing_var_atomic_type instanceof TMixed + || $existing_var_atomic_type instanceof TScalar + || $existing_var_atomic_type instanceof TNumeric + ) { + if ($is_loose_equality) { + return $existing_var_type; + } + + $asserted_type = new Union([new TLiteralFloat($value)]); + $asserted_type->from_docblock = $existing_var_type->from_docblock; + return $asserted_type; + } + } + + return null; + } + /** * @param array> $template_type_map * @param array $suppressed_issues From 57e054931eca212eeb06846b2641ea39c45c6eee Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 20:01:34 +0100 Subject: [PATCH 16/19] remove unused param --- src/Psalm/Internal/Type/AssertionReconciler.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 0787fe77def..331397dc3c9 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1045,7 +1045,6 @@ private static function handleLiteralEquality( return self::handleLiteralEqualityWithFloat( $statements_analyzer, $assertion, - $scalar_type, $bracket_pos, $is_loose_equality, $existing_var_type, @@ -1430,7 +1429,6 @@ private static function handleLiteralEqualityWithString( private static function handleLiteralEqualityWithFloat( StatementsAnalyzer $statements_analyzer, string $assertion, - string $scalar_type, int $bracket_pos, bool $is_loose_equality, Union $existing_var_type, From c22670d851280567ebe4d01f09da0eecf5c537a2 Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 20:05:56 +0100 Subject: [PATCH 17/19] use the correct type for float function --- src/Psalm/Internal/Type/AssertionReconciler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 331397dc3c9..3c90d2d7dc3 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1636,7 +1636,7 @@ private static function getCompatibleStringType( private static function getCompatibleFloatType( Union $existing_var_type, array $existing_var_atomic_types, - int $value, + float $value, bool $is_loose_equality ): ?Union { foreach ($existing_var_atomic_types as $existing_var_atomic_type) { From 27c4b07d5b3310ef34b67400542ef375fe11f416 Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 5 Jan 2022 22:11:22 +0100 Subject: [PATCH 18/19] fix wrong not literal loose check --- src/Psalm/Internal/Type/AssertionReconciler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 3c90d2d7dc3..d6a7330a147 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1540,8 +1540,8 @@ private static function handleLiteralEqualityWithFloat( return $existing_var_type; } - if ($existing_var_atomic_type instanceof TFloat - && !$existing_var_atomic_type instanceof TLiteralFloat + if ($existing_var_atomic_type instanceof TString + && !$existing_var_atomic_type instanceof TLiteralString ) { return $existing_var_type; } From 6296a605531e3897af1df22fae9a2a16b4919d3c Mon Sep 17 00:00:00 2001 From: orklah Date: Fri, 7 Jan 2022 18:18:47 +0100 Subject: [PATCH 19/19] add new test that was failing before --- tests/IntRangeTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/IntRangeTest.php b/tests/IntRangeTest.php index 5b7cf913eaf..93ac1d97ce0 100644 --- a/tests/IntRangeTest.php +++ b/tests/IntRangeTest.php @@ -680,6 +680,9 @@ function doAnalysis(): void assert($length === 1); ', + 'assertions' => [ + '$length===' => '1', + ], ], ]; }