diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 4270844759e..562b94c110e 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $comment_block->tags['variablesfrom'][0] @@ -296,6 +296,12 @@ $b[$y] + + + $exploded[1] + $url + + $stmt->props[0] @@ -379,9 +385,9 @@ $fixed_type_tokens[$i - 1] - + $source_param_string - + @@ -695,6 +701,8 @@ allFloatLiterals allFloatLiterals + hasLowercaseString + hasLowercaseString diff --git a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php index 37bd7befa5b..bdd59a0abc4 100644 --- a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php @@ -25,7 +25,6 @@ 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 @@ public function __construct() $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); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php deleted file mode 100644 index f1d3363fd11..00000000000 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php +++ /dev/null @@ -1,103 +0,0 @@ - - */ - 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(); - } -} diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index 92e1e9d7de3..10719d3bbd1 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -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 + * ? list + * : ( + * $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 + * ) + * ) + * ) + * ) + * : ( + * $limit is int + * ? list + * : ( + * $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 + * ) + * ) + * ) + * ) + * ) + * * @psalm-flow ($string) -(array-assignment)-> return */ function explode(string $separator, string $string, int $limit = -1) : array {} diff --git a/tests/AssertAnnotationTest.php b/tests/AssertAnnotationTest.php index 0cadd524cea..8630d068a87 100644 --- a/tests/AssertAnnotationTest.php +++ b/tests/AssertAnnotationTest.php @@ -1006,7 +1006,7 @@ function isStringTuple($value): void { } } - $s = ""; + $s = "Hello World!"; $parts = explode(":", $s, 2); diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index 71afb79e99e..201210cc0dd 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -422,7 +422,7 @@ public static function Baz($mixed) : string { /** @var string $string */ $elements = explode(" ", $string, -5);', 'assertions' => [ - '$elements' => 'list', + '$elements' => 'list', ], ], 'explodeWithDynamicLimit' => [ @@ -433,52 +433,63 @@ public static function Baz($mixed) : string { */ $elements = explode(" ", $string, $limit);', 'assertions' => [ - '$elements' => 'list', + '$elements' => 'list{0?: string, 1?: string, 2?: string, ..., string>}', ], ], 'explodeWithDynamicDelimiter' => [ 'code' => ' [ - '$elements' => 'false|non-empty-list', + '$elements' => 'non-empty-list', + ], + ], + 'explodeWithDynamicDelimiterAndSmallPositiveLimit' => [ + 'code' => ' [ + '$elements' => 'list{0: string, 1?: string}', ], ], 'explodeWithDynamicDelimiterAndPositiveLimit' => [ 'code' => ' [ - '$elements' => 'false|non-empty-list', + '$elements' => 'non-empty-list', ], ], 'explodeWithDynamicDelimiterAndNegativeLimit' => [ 'code' => ' [ - '$elements' => 'false|list', + '$elements' => 'list', ], ], 'explodeWithDynamicDelimiterAndLimit' => [ 'code' => ' [ - '$elements' => 'false|list', + '$elements' => 'list{0?: string, 1?: string, 2?: string, ..., string>}', ], ], 'explodeWithDynamicNonEmptyDelimiter' => [ @@ -502,19 +513,12 @@ public static function Baz($mixed) : string { '$elements' => 'non-empty-list', ], ], - 'explodeWithLiteralEmptyDelimiter' => [ + 'explodeWithPossiblyFalse' => [ 'code' => ' */ - $elements = explode("", $string);', - 'assertions' => [ - '$elements' => 'false', - ], - ], - 'explodeWithPossiblyFalse' => [ - 'code' => ' */ function exploder(string $d, string $s) : array { return explode($d, $s); }', @@ -2232,7 +2236,7 @@ function a($b): int function exploder(string $s) : array { return explode("", $s); }', - 'error_message' => 'FalsableReturnStatement', + 'error_message' => 'InvalidArgument', ], 'complainAboutArrayToIterable' => [ 'code' => ' 'InvalidArrayOffset', ], - 'listDestructuringErrorSuppress' => [ - 'code' => ' 'NullableReturnStatement', - ], 'undefinedVarInNullCoalesce' => [ 'code' => '