1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 13:51:54 +01:00

Merge pull request #10545 from kkmuffme/basename-dirname-return-type-more-specific

make basename & dirname return types more specific
This commit is contained in:
orklah 2024-01-14 23:39:59 +01:00 committed by GitHub
commit 48ef3c474c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 120 additions and 15 deletions

View File

@ -44,7 +44,48 @@ final class BasenameReturnTypeProvider implements FunctionReturnTypeProviderInte
);
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);

View File

@ -9,7 +9,6 @@ use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
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 @@ final class DirnameReturnTypeProvider implements FunctionReturnTypeProviderInter
$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 @@ final class DirnameReturnTypeProvider implements FunctionReturnTypeProviderInter
$atomic_type->value > 0) {
$dir_level = $atomic_type->value;
} else {
return Type::getString();
return $fallback_type;
}
}
}
@ -63,17 +97,7 @@ final class DirnameReturnTypeProvider implements FunctionReturnTypeProviderInter
);
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);

View File

@ -32,5 +32,25 @@ class BasenameTest extends TestCase
'$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',
],
];
}
}

View File

@ -49,7 +49,27 @@ class DirnameTest extends TestCase
$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',
],
];
}