From f035c00a21c6dfb3d6887b56d1da524c655dd19d Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:32:52 +0100 Subject: [PATCH 1/2] Fix non-empty-lowercase-string handling with literal non-lowercase strings * Fix https://github.com/vimeo/psalm/issues/9782 and related issues * add explicit handling for non-falsy-string to not fallback non-falsy-string and 0 to string --- src/Psalm/Internal/Type/TypeCombiner.php | 39 ++++++++++++++++++++++-- tests/TypeCombinationTest.php | 34 +++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index e21d41b05..0a084e8a6 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -1103,18 +1103,53 @@ final class TypeCombiner } else { $combination->value_types['string'] = $type; } - } elseif ($type instanceof TNonEmptyString) { + } elseif ($type instanceof TNonFalsyString) { $has_empty_string = false; + $has_falsy_string = false; foreach ($combination->strings as $string_type) { - if (!$string_type->value) { + if ($string_type->value === '') { $has_empty_string = true; + $has_falsy_string = true; break; } + + if ($string_type->value === '0') { + $has_falsy_string = true; + } } if ($has_empty_string) { $combination->value_types['string'] = new TString(); + } elseif ($has_falsy_string) { + $combination->value_types['string'] = new TNonEmptyString(); + } else { + $combination->value_types['string'] = $type; + } + } elseif ($type instanceof TNonEmptyString) { + $has_empty_string = false; + + foreach ($combination->strings as $string_type) { + if ($string_type->value === '') { + $has_empty_string = true; + break; + } + } + + $has_non_lowercase_string = false; + if ($type instanceof TNonEmptyLowercaseString) { + foreach ($combination->strings as $string_type) { + if (strtolower($string_type->value) !== $string_type->value) { + $has_non_lowercase_string = true; + break; + } + } + } + + if ($has_empty_string) { + $combination->value_types['string'] = new TString(); + } elseif ($has_non_lowercase_string && get_class($type) !== TNonEmptyString::class) { + $combination->value_types['string'] = new TNonEmptyString(); } else { $combination->value_types['string'] = $type; } diff --git a/tests/TypeCombinationTest.php b/tests/TypeCombinationTest.php index 56af82a69..ba3ded5e8 100644 --- a/tests/TypeCombinationTest.php +++ b/tests/TypeCombinationTest.php @@ -127,6 +127,40 @@ class TypeCombinationTest extends TestCase '$x===' => 'non-falsy-string', ], ], + 'loopNonFalsyWithZeroShouldBeNonEmpty' => [ + 'code' => ' [ + '$x===' => 'list', + ], + ], + 'loopNonLowercaseLiteralWithNonEmptyLowercaseShouldBeNonEmptyAndNotLowercase' => [ + 'code' => ' [ + '$x===' => 'list', + ], + ], ]; } From 4ac18720aab9dda3c3fc3d5a2d55d8ce2fdf5150 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:41:29 +0100 Subject: [PATCH 2/2] Revert https://github.com/vimeo/psalm/pull/10039 and fix type and test --- src/Psalm/Internal/Type/TypeCombiner.php | 15 +++++++++++---- tests/TypeCombinationTest.php | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index 0a084e8a6..099ae82c7 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -1047,12 +1047,19 @@ final class TypeCombiner && strtolower($type->value) === $type->value ) { // do nothing + } elseif (isset($combination->value_types['string']) + && $combination->value_types['string'] instanceof TNonFalsyString + && $type->value + ) { + // do nothing + } elseif (isset($combination->value_types['string']) + && $combination->value_types['string'] instanceof TNonFalsyString + && $type->value === '0' + ) { + $combination->value_types['string'] = new TNonEmptyString(); } elseif (isset($combination->value_types['string']) && $combination->value_types['string'] instanceof TNonEmptyString - && ($combination->value_types['string'] instanceof TNonFalsyString - ? $type->value - : $type->value !== '' - ) + && $type->value !== '' ) { // do nothing } else { diff --git a/tests/TypeCombinationTest.php b/tests/TypeCombinationTest.php index ba3ded5e8..841fbd67e 100644 --- a/tests/TypeCombinationTest.php +++ b/tests/TypeCombinationTest.php @@ -934,7 +934,7 @@ class TypeCombinationTest extends TestCase ], ], 'nonFalsyStringAndFalsyLiteral' => [ - 'string', + 'non-empty-string', [ 'non-falsy-string', '"0"',