Skip to content

Commit

Permalink
Merge pull request #10545 from kkmuffme/basename-dirname-return-type-…
Browse files Browse the repository at this point in the history
…more-specific

make basename & dirname return types more specific
  • Loading branch information
orklah committed Jan 14, 2024
2 parents 079bfb8 + f940c02 commit 48ef3c4
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
}
}
Expand All @@ -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);
Expand Down
20 changes: 20 additions & 0 deletions tests/ReturnTypeProvider/BasenameTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,25 @@ public function providerValidCodeParse(): iterable
'$base===' => 'string',
],
];

yield 'basenameOfStringPathReturnsNonEmptyString' => [
'code' => '<?php
$foo = rand(0, 1) ? "0" : "world";
$base = basename($foo);
',
'assertions' => [
'$base===' => 'non-empty-string',
],
];

yield 'basenameOfStringPathReturnsNonFalsyString' => [
'code' => '<?php
$foo = rand(0, 1) ? "hello" : "world";
$base = basename($foo);
',
'assertions' => [
'$base===' => 'non-falsy-string',
],
];
}
}
22 changes: 21 additions & 1 deletion tests/ReturnTypeProvider/DirnameTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' => '<?php
$foo = rand(0, 1) ? "0" : "world";
$dir = dirname($foo, 20);
',
'assertions' => [
'$dir===' => 'non-falsy-string',
],
];

yield 'dirnameOfEmptyShouldBeString' => [
'code' => '<?php
$foo = rand(0, 1) ? "" : "world";
$dir = dirname($foo, 20);
',
'assertions' => [
'$dir===' => 'string',
],
];
}
Expand Down

0 comments on commit 48ef3c4

Please sign in to comment.