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] 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 | 37 +++++++++++++++++++++++- tests/TypeCombinationTest.php | 34 ++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index e21d41b0559..0a084e8a62f 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -1103,18 +1103,53 @@ private static function scrapeStringProperties( } else { $combination->value_types['string'] = $type; } + } elseif ($type instanceof TNonFalsyString) { + $has_empty_string = false; + $has_falsy_string = false; + + foreach ($combination->strings as $string_type) { + 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) { + 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 56af82a69ed..ba3ded5e83d 100644 --- a/tests/TypeCombinationTest.php +++ b/tests/TypeCombinationTest.php @@ -127,6 +127,40 @@ function takesLiteralString($arg) {} '$x===' => 'non-falsy-string', ], ], + 'loopNonFalsyWithZeroShouldBeNonEmpty' => [ + 'code' => ' [ + '$x===' => 'list', + ], + ], + 'loopNonLowercaseLiteralWithNonEmptyLowercaseShouldBeNonEmptyAndNotLowercase' => [ + 'code' => ' [ + '$x===' => 'list', + ], + ], ]; }