mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 12:24:49 +01:00
Merge pull request #9016 from Ocramius/feature/#5039-more-refined-types-for-explode-core-function
Refined `explode()` types
This commit is contained in:
commit
790c30959d
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<files psalm-version="dev-master@6fc9e50b9765d573db796e81522af759bc6987a5">
|
||||
<files psalm-version="dev-master@dbcfe62c5224603912c94c1eab5d7c31841ada82">
|
||||
<file src="examples/TemplateChecker.php">
|
||||
<PossiblyUndefinedIntArrayOffset>
|
||||
<code>$comment_block->tags['variablesfrom'][0]</code>
|
||||
@ -296,6 +296,12 @@
|
||||
<code>$b[$y]</code>
|
||||
</InvalidArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php">
|
||||
<PossiblyUndefinedArrayOffset>
|
||||
<code>$exploded[1]</code>
|
||||
<code>$url</code>
|
||||
</PossiblyUndefinedArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php">
|
||||
<PossiblyUndefinedIntArrayOffset>
|
||||
<code>$stmt->props[0]</code>
|
||||
@ -379,9 +385,9 @@
|
||||
<PossiblyInvalidArrayOffset>
|
||||
<code>$fixed_type_tokens[$i - 1]</code>
|
||||
</PossiblyInvalidArrayOffset>
|
||||
<PossiblyUndefinedIntArrayOffset>
|
||||
<PossiblyUndefinedArrayOffset>
|
||||
<code>$source_param_string</code>
|
||||
</PossiblyUndefinedIntArrayOffset>
|
||||
</PossiblyUndefinedArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php">
|
||||
<PossiblyUndefinedIntArrayOffset>
|
||||
@ -695,6 +701,8 @@
|
||||
<PossiblyUnusedMethod>
|
||||
<code>allFloatLiterals</code>
|
||||
<code>allFloatLiterals</code>
|
||||
<code>hasLowercaseString</code>
|
||||
<code>hasLowercaseString</code>
|
||||
</PossiblyUnusedMethod>
|
||||
</file>
|
||||
<file src="tests/Internal/Codebase/InternalCallMapHandlerTest.php">
|
||||
|
@ -25,7 +25,6 @@ use Psalm\Internal\Provider\ReturnTypeProvider\ArraySpliceReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\ArrayUniqueReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\BasenameReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\DirnameReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\ExplodeReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\FilterVarReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\FirstArgStringReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\GetClassMethodsReturnTypeProvider;
|
||||
@ -94,7 +93,6 @@ class FunctionReturnTypeProvider
|
||||
$this->registerClass(MktimeReturnTypeProvider::class);
|
||||
$this->registerClass(BasenameReturnTypeProvider::class);
|
||||
$this->registerClass(DirnameReturnTypeProvider::class);
|
||||
$this->registerClass(ExplodeReturnTypeProvider::class);
|
||||
$this->registerClass(GetObjectVarsReturnTypeProvider::class);
|
||||
$this->registerClass(GetClassMethodsReturnTypeProvider::class);
|
||||
$this->registerClass(FirstArgStringReturnTypeProvider::class);
|
||||
|
@ -1,103 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Internal\Provider\ReturnTypeProvider;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
|
||||
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TLowercaseString;
|
||||
use Psalm\Type\Atomic\TNonEmptyString;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ExplodeReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
{
|
||||
/**
|
||||
* @return array<lowercase-string>
|
||||
*/
|
||||
public static function getFunctionIds(): array
|
||||
{
|
||||
return ['explode'];
|
||||
}
|
||||
|
||||
public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): Union
|
||||
{
|
||||
$statements_source = $event->getStatementsSource();
|
||||
$call_args = $event->getCallArgs();
|
||||
if (!$statements_source instanceof StatementsAnalyzer) {
|
||||
return Type::getMixed();
|
||||
}
|
||||
|
||||
if (count($call_args) >= 2) {
|
||||
$second_arg_type = $statements_source->node_data->getType($call_args[1]->value);
|
||||
|
||||
$inner_type = new Union([
|
||||
$second_arg_type && $second_arg_type->hasLowercaseString()
|
||||
? new TLowercaseString()
|
||||
: new TString,
|
||||
]);
|
||||
|
||||
$can_return_empty = isset($call_args[2])
|
||||
&& (
|
||||
!($third_arg_type = $statements_source->node_data->getType($call_args[2]->value))
|
||||
|| !$third_arg_type->isSingleIntLiteral()
|
||||
|| $third_arg_type->getSingleIntLiteral()->value < 0
|
||||
);
|
||||
|
||||
if ($call_args[0]->value instanceof PhpParser\Node\Scalar\String_) {
|
||||
if ($call_args[0]->value->value === '') {
|
||||
return Type::getFalse();
|
||||
}
|
||||
|
||||
return new Union([
|
||||
$can_return_empty
|
||||
? Type::getListAtomic($inner_type)
|
||||
: Type::getNonEmptyListAtomic($inner_type),
|
||||
]);
|
||||
}
|
||||
|
||||
if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value))
|
||||
&& $first_arg_type->hasString()) {
|
||||
$can_be_false = true;
|
||||
if ($first_arg_type->isString()) {
|
||||
$can_be_false = false;
|
||||
foreach ($first_arg_type->getAtomicTypes() as $string_type) {
|
||||
if (!($string_type instanceof TNonEmptyString)) {
|
||||
$can_be_false = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($can_be_false) {
|
||||
$array_type = new Union([
|
||||
$can_return_empty
|
||||
? Type::getListAtomic($inner_type)
|
||||
: Type::getNonEmptyListAtomic($inner_type),
|
||||
new TFalse,
|
||||
], [
|
||||
'ignore_falsable_issues' =>
|
||||
$statements_source->getCodebase()->config->ignore_internal_falsable_issues,
|
||||
]);
|
||||
} else {
|
||||
$array_type = new Union([
|
||||
$can_return_empty
|
||||
? Type::getListAtomic($inner_type)
|
||||
: Type::getNonEmptyListAtomic($inner_type),
|
||||
]);
|
||||
}
|
||||
|
||||
return $array_type;
|
||||
}
|
||||
}
|
||||
|
||||
return Type::getMixed();
|
||||
}
|
||||
}
|
@ -725,6 +725,46 @@ function join($separator, array $array = []): string
|
||||
/**
|
||||
* @psalm-pure
|
||||
*
|
||||
* @param non-empty-string $separator
|
||||
*
|
||||
* @return (
|
||||
* $string is lowercase-string
|
||||
* ? (
|
||||
* $limit is int<min, -2>
|
||||
* ? list<empty>
|
||||
* : (
|
||||
* $limit is int<0, 1>
|
||||
* ? list{lowercase-string}
|
||||
* : (
|
||||
* $limit is 2
|
||||
* ? list{0: lowercase-string, 1?: lowercase-string}
|
||||
* : (
|
||||
* $limit is 3
|
||||
* ? list{0: lowercase-string, 1?: lowercase-string, 2?: lowercase-string}
|
||||
* : non-empty-list<lowercase-string>
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* : (
|
||||
* $limit is int<min, -2>
|
||||
* ? list<empty>
|
||||
* : (
|
||||
* $limit is int<0, 1>
|
||||
* ? list{string}
|
||||
* : (
|
||||
* $limit is 2
|
||||
* ? list{0: string, 1?: string}
|
||||
* : (
|
||||
* $limit is 3
|
||||
* ? list{0: string, 1?: string, 2?: string}
|
||||
* : non-empty-list<string>
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @psalm-flow ($string) -(array-assignment)-> return
|
||||
*/
|
||||
function explode(string $separator, string $string, int $limit = -1) : array {}
|
||||
|
@ -1006,7 +1006,7 @@ class AssertAnnotationTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
$s = "";
|
||||
$s = "Hello World!";
|
||||
|
||||
$parts = explode(":", $s, 2);
|
||||
|
||||
|
@ -422,7 +422,7 @@ class FunctionCallTest extends TestCase
|
||||
/** @var string $string */
|
||||
$elements = explode(" ", $string, -5);',
|
||||
'assertions' => [
|
||||
'$elements' => 'list<string>',
|
||||
'$elements' => 'list<never>',
|
||||
],
|
||||
],
|
||||
'explodeWithDynamicLimit' => [
|
||||
@ -433,52 +433,63 @@ class FunctionCallTest extends TestCase
|
||||
*/
|
||||
$elements = explode(" ", $string, $limit);',
|
||||
'assertions' => [
|
||||
'$elements' => 'list<string>',
|
||||
'$elements' => 'list{0?: string, 1?: string, 2?: string, ...<int<0, max>, string>}',
|
||||
],
|
||||
],
|
||||
'explodeWithDynamicDelimiter' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @var string $delim
|
||||
* @var non-empty-string $delim
|
||||
* @var string $string
|
||||
*/
|
||||
$elements = explode($delim, $string);',
|
||||
'assertions' => [
|
||||
'$elements' => 'false|non-empty-list<string>',
|
||||
'$elements' => 'non-empty-list<string>',
|
||||
],
|
||||
],
|
||||
'explodeWithDynamicDelimiterAndSmallPositiveLimit' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @var non-empty-string $delim
|
||||
* @var string $string
|
||||
*/
|
||||
$elements = explode($delim, $string, 2);',
|
||||
'assertions' => [
|
||||
'$elements' => 'list{0: string, 1?: string}',
|
||||
],
|
||||
],
|
||||
'explodeWithDynamicDelimiterAndPositiveLimit' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @var string $delim
|
||||
* @var non-empty-string $delim
|
||||
* @var string $string
|
||||
*/
|
||||
$elements = explode($delim, $string, 5);',
|
||||
'assertions' => [
|
||||
'$elements' => 'false|non-empty-list<string>',
|
||||
'$elements' => 'non-empty-list<string>',
|
||||
],
|
||||
],
|
||||
'explodeWithDynamicDelimiterAndNegativeLimit' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @var string $delim
|
||||
* @var non-empty-string $delim
|
||||
* @var string $string
|
||||
*/
|
||||
$elements = explode($delim, $string, -5);',
|
||||
'assertions' => [
|
||||
'$elements' => 'false|list<string>',
|
||||
'$elements' => 'list<never>',
|
||||
],
|
||||
],
|
||||
'explodeWithDynamicDelimiterAndLimit' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @var string $delim
|
||||
* @var non-empty-string $delim
|
||||
* @var string $string
|
||||
* @var int $limit
|
||||
*/
|
||||
$elements = explode($delim, $string, $limit);',
|
||||
'assertions' => [
|
||||
'$elements' => 'false|list<string>',
|
||||
'$elements' => 'list{0?: string, 1?: string, 2?: string, ...<int<0, max>, string>}',
|
||||
],
|
||||
],
|
||||
'explodeWithDynamicNonEmptyDelimiter' => [
|
||||
@ -502,19 +513,12 @@ class FunctionCallTest extends TestCase
|
||||
'$elements' => 'non-empty-list<string>',
|
||||
],
|
||||
],
|
||||
'explodeWithLiteralEmptyDelimiter' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @var string $string
|
||||
*/
|
||||
$elements = explode("", $string);',
|
||||
'assertions' => [
|
||||
'$elements' => 'false',
|
||||
],
|
||||
],
|
||||
'explodeWithPossiblyFalse' => [
|
||||
'code' => '<?php
|
||||
/** @return non-empty-list<string> */
|
||||
/**
|
||||
* @param non-empty-string $d
|
||||
* @return non-empty-list<string>
|
||||
*/
|
||||
function exploder(string $d, string $s) : array {
|
||||
return explode($d, $s);
|
||||
}',
|
||||
@ -2232,7 +2236,7 @@ class FunctionCallTest extends TestCase
|
||||
function exploder(string $s) : array {
|
||||
return explode("", $s);
|
||||
}',
|
||||
'error_message' => 'FalsableReturnStatement',
|
||||
'error_message' => 'InvalidArgument',
|
||||
],
|
||||
'complainAboutArrayToIterable' => [
|
||||
'code' => '<?php
|
||||
|
@ -1065,15 +1065,6 @@ class IssetTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'InvalidArrayOffset',
|
||||
],
|
||||
'listDestructuringErrorSuppress' => [
|
||||
'code' => '<?php
|
||||
function foo(string $s) : string {
|
||||
/** @psalm-suppress PossiblyUndefinedArrayOffset */
|
||||
@list($port) = explode(":", $s, -1);
|
||||
return $port;
|
||||
}',
|
||||
'error_message' => 'NullableReturnStatement',
|
||||
],
|
||||
'undefinedVarInNullCoalesce' => [
|
||||
'code' => '<?php
|
||||
function bar(): void {
|
||||
|
Loading…
Reference in New Issue
Block a user