Skip to content

Commit

Permalink
Merge pull request #9534 from fluffycondor/php8-str-function-signatures
Browse files Browse the repository at this point in the history
Improve PHP8 str_* function signatures
  • Loading branch information
orklah committed Mar 27, 2023
2 parents 49cf5d0 + 9730f2c commit 6ca2f09
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 1 deletion.
Expand Up @@ -71,7 +71,9 @@
use function strpos;
use function strtolower;
use function substr;
use function substr_count;

use const DIRECTORY_SEPARATOR;
use const PREG_SPLIT_NO_EMPTY;

/**
Expand Down Expand Up @@ -174,7 +176,9 @@ public static function checkArgumentMatches(
$prev_ord = $ord;
}

if (count($values) < 12 || ($gt_count / count($values)) < 0.8) {
if (substr_count($arg_value_type->getSingleStringLiteral()->value, DIRECTORY_SEPARATOR) <= 2
&& (count($values) < 12 || ($gt_count / count($values)) < 0.8)
) {
IssueBuffer::maybeAdd(
new InvalidLiteralArgument(
'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id
Expand Down
27 changes: 27 additions & 0 deletions stubs/CoreGenericFunctions.phpstub
Expand Up @@ -1057,6 +1057,33 @@ function str_shuffle(string $string): string {}
*/
function str_split(string $string, int $length = 1) {}

/**
* @psalm-pure
* @template T as string
* @param T $needle
* @psalm-assert-if-true =(T is '' ? string : non-empty-string) $haystack
* @return ($needle is '' ? true : ($haystack is '' ? false : bool))
*/
function str_starts_with(string $haystack, string $needle): bool {}

/**
* @psalm-pure
* @template T as string
* @param T $needle
* @psalm-assert-if-true =(T is '' ? string : non-empty-string) $haystack
* @return ($needle is '' ? true : ($haystack is '' ? false : bool))
*/
function str_ends_with(string $haystack, string $needle): bool {}

/**
* @psalm-pure
* @template T as string
* @param T $needle
* @psalm-assert-if-true =(T is '' ? string : non-empty-string) $haystack
* @return ($needle is '' ? true : ($haystack is '' ? false : bool))
*/
function str_contains(string $haystack, string $needle): bool {}

/**
* @psalm-pure
* @return string|false
Expand Down
135 changes: 135 additions & 0 deletions tests/CoreStubsTest.php
Expand Up @@ -151,6 +151,123 @@ function foo(string $foo): string
'$f===' => 'false|non-empty-string',
],
];
yield 'str_starts_with/str_ends_with/str_contains redundant condition detection' => [
'code' => '<?php
$a1 = str_starts_with(uniqid(), "");
/** @psalm-suppress InvalidLiteralArgument */
$b1 = str_starts_with("", "random string");
$c1 = str_starts_with(uniqid(), "random string");
$a2 = str_ends_with(uniqid(), "");
/** @psalm-suppress InvalidLiteralArgument */
$b2 = str_ends_with("", "random string");
$c2 = str_ends_with(uniqid(), "random string");
$a3 = str_contains(uniqid(), "");
/** @psalm-suppress InvalidLiteralArgument */
$b3 = str_contains("", "random string");
$c3 = str_contains(uniqid(), "random string");
',
'assertions' => [
'$a1===' => 'true',
'$b1===' => 'false',
'$c1===' => 'bool',
'$a2===' => 'true',
'$b2===' => 'false',
'$c2===' => 'bool',
'$a3===' => 'true',
'$b3===' => 'false',
'$c3===' => 'bool',
],
];
yield 'PHP8 str_* function assert non-empty-string' => [
'code' => '<?php
/** @return non-empty-string */
function after_str_contains(): string
{
$string = file_get_contents("");
if (str_contains($string, "foo")) {
return $string;
}
throw new RuntimeException();
}
/** @return non-empty-string */
function after_str_starts_with(): string
{
$string = file_get_contents("");
if (str_starts_with($string, "foo")) {
return $string;
}
throw new RuntimeException();
}
/** @return non-empty-string */
function after_str_ends_with(): string
{
$string = file_get_contents("");
if (str_ends_with($string, "foo")) {
return $string;
}
throw new RuntimeException();
}
$a = after_str_contains();
$b = after_str_starts_with();
$c = after_str_ends_with();
',
'assertions' => [
'$a===' => 'non-empty-string',
'$b===' => 'non-empty-string',
'$c===' => 'non-empty-string',
],
];
yield "PHP8 str_* function doesn't subtract string after assertion" => [
'code' => '<?php
/** @return false|string */
function after_str_contains()
{
$string = file_get_contents("");
if (!str_contains($string, "foo")) {
return $string;
}
throw new RuntimeException();
}
/** @return false|string */
function after_str_starts_with()
{
$string = file_get_contents("");
if (!str_starts_with($string, "foo")) {
return $string;
}
throw new RuntimeException();
}
/** @return false|string */
function after_str_ends_with()
{
$string = file_get_contents("");
if (!str_ends_with($string, "foo")) {
return $string;
}
throw new RuntimeException();
}
$a = after_str_contains();
$b = after_str_starts_with();
$c = after_str_ends_with();
',
'assertions' => [
'$a===' => 'false|string',
'$b===' => 'false|string',
'$c===' => 'false|string',
],
];
yield "str_contains doesn't yield InvalidLiteralArgument for __DIR__" => [
'code' => '<?php
$d = __DIR__;
echo str_contains($d, "psalm");
',
];
}

public function providerInvalidCodeParse(): iterable
Expand All @@ -167,5 +284,23 @@ public function providerInvalidCodeParse(): iterable
',
'error_message' => 'InvalidArgument',
];
yield 'str_contains literal haystack' => [
'code' => '<?php
str_contains("literal", "");
',
'error_message' => 'InvalidLiteralArgument',
];
yield 'str_starts_with literal haystack' => [
'code' => '<?php
str_starts_with("literal", "");
',
'error_message' => 'InvalidLiteralArgument',
];
yield 'str_ends_with literal haystack' => [
'code' => '<?php
str_ends_with("literal", "");
',
'error_message' => 'InvalidLiteralArgument',
];
}
}

0 comments on commit 6ca2f09

Please sign in to comment.