diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/BasenameReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/BasenameReturnTypeProvider.php index 8e11092326e..487f1dcffb4 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/BasenameReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/BasenameReturnTypeProvider.php @@ -44,7 +44,48 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev ); if ($evaled_path === null) { - return Type::getString(); + $union = $statements_source->getNodeTypeProvider()->getType($call_args[0]->value); + $generic = false; + $non_empty = false; + if ($union !== null) { + foreach ($union->getAtomicTypes() as $atomic) { + if ($atomic instanceof Type\Atomic\TNonFalsyString) { + continue; + } + + if ($atomic instanceof Type\Atomic\TLiteralString) { + if ($atomic->value === '') { + $generic = true; + break; + } + + if ($atomic->value === '0') { + $non_empty = true; + continue; + } + + continue; + } + + if ($atomic instanceof Type\Atomic\TNonEmptyString) { + $non_empty = true; + continue; + } + + $generic = true; + break; + } + } + + if ($union === null || $generic) { + return Type::getString(); + } + + if ($non_empty) { + return Type::getNonEmptyString(); + } + + return Type::getNonFalsyString(); } $basename = basename($evaled_path); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/DirnameReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/DirnameReturnTypeProvider.php index f6c63c47ac4..ff4fbc54fed 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/DirnameReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/DirnameReturnTypeProvider.php @@ -9,7 +9,6 @@ use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface; use Psalm\Type; use Psalm\Type\Atomic\TLiteralInt; -use Psalm\Type\Atomic\TNonEmptyString; use Psalm\Type\Union; use function array_values; @@ -39,6 +38,41 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $statements_source = $event->getStatementsSource(); $node_type_provider = $statements_source->getNodeTypeProvider(); + $union = $node_type_provider->getType($call_args[0]->value); + $generic = false; + if ($union !== null) { + foreach ($union->getAtomicTypes() as $atomic) { + if ($atomic instanceof Type\Atomic\TNonFalsyString) { + continue; + } + + if ($atomic instanceof Type\Atomic\TLiteralString) { + if ($atomic->value === '') { + $generic = true; + break; + } + + // 0 will be non-falsy too (.) + continue; + } + + if ($atomic instanceof Type\Atomic\TNonEmptyString + || $atomic instanceof Type\Atomic\TEmptyNumeric) { + continue; + } + + // generic string is the only other possible case of empty string + // which would result in a generic string + $generic = true; + break; + } + } + + $fallback_type = Type::getNonFalsyString(); + if ($union === null || $generic) { + $fallback_type = Type::getString(); + } + $dir_level = 1; if (isset($call_args[1])) { $type = $node_type_provider->getType($call_args[1]->value); @@ -49,7 +83,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $atomic_type->value > 0) { $dir_level = $atomic_type->value; } else { - return Type::getString(); + return $fallback_type; } } } @@ -63,17 +97,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev ); if ($evaled_path === null) { - $type = $node_type_provider->getType($call_args[0]->value); - if ($type !== null && $type->isSingle()) { - $atomic_type = array_values($type->getAtomicTypes())[0]; - if ($atomic_type instanceof TNonEmptyString) { - return Type::getNonEmptyString(); - } - } - } - - if ($evaled_path === null) { - return Type::getString(); + return $fallback_type; } $path_to_file = dirname($evaled_path, $dir_level); diff --git a/tests/ReturnTypeProvider/BasenameTest.php b/tests/ReturnTypeProvider/BasenameTest.php index 5c624651f69..89ab1562581 100644 --- a/tests/ReturnTypeProvider/BasenameTest.php +++ b/tests/ReturnTypeProvider/BasenameTest.php @@ -32,5 +32,25 @@ public function providerValidCodeParse(): iterable '$base===' => 'string', ], ]; + + yield 'basenameOfStringPathReturnsNonEmptyString' => [ + 'code' => ' [ + '$base===' => 'non-empty-string', + ], + ]; + + yield 'basenameOfStringPathReturnsNonFalsyString' => [ + 'code' => ' [ + '$base===' => 'non-falsy-string', + ], + ]; } } diff --git a/tests/ReturnTypeProvider/DirnameTest.php b/tests/ReturnTypeProvider/DirnameTest.php index 48dea4d2cca..98d7b40f722 100644 --- a/tests/ReturnTypeProvider/DirnameTest.php +++ b/tests/ReturnTypeProvider/DirnameTest.php @@ -49,7 +49,27 @@ public function providerValidCodeParse(): iterable $dir = dirname(uniqid() . "abc", 2); ', 'assertions' => [ - '$dir===' => 'non-empty-string', + '$dir===' => 'non-falsy-string', + ], + ]; + + yield 'dirnameOfNonEmptyShouldBeNonFalsy' => [ + 'code' => ' [ + '$dir===' => 'non-falsy-string', + ], + ]; + + yield 'dirnameOfEmptyShouldBeString' => [ + 'code' => ' [ + '$dir===' => 'string', ], ]; }