1
0
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:
orklah 2022-12-29 10:23:18 +01:00 committed by GitHub
commit 790c30959d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 78 additions and 140 deletions

View File

@ -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-&gt;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-&gt;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">

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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 {}

View File

@ -1006,7 +1006,7 @@ class AssertAnnotationTest extends TestCase
}
}
$s = "";
$s = "Hello World!";
$parts = explode(":", $s, 2);

View File

@ -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

View File

@ -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 {