1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Merge pull request #7027 from rarila/issue-6914

This commit is contained in:
Bruce Weirdan 2021-11-30 23:34:00 +02:00 committed by GitHub
commit 28c4f86993
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 85 additions and 17 deletions

View File

@ -10273,7 +10273,7 @@ return [
'preg_filter' => ['null|string|string[]', 'pattern'=>'mixed', 'replacement'=>'mixed', 'subject'=>'mixed', 'limit='=>'int', '&w_count='=>'int'], 'preg_filter' => ['null|string|string[]', 'pattern'=>'mixed', 'replacement'=>'mixed', 'subject'=>'mixed', 'limit='=>'int', '&w_count='=>'int'],
'preg_grep' => ['array|false', 'pattern'=>'string', 'array'=>'array', 'flags='=>'int'], 'preg_grep' => ['array|false', 'pattern'=>'string', 'array'=>'array', 'flags='=>'int'],
'preg_last_error' => ['int'], 'preg_last_error' => ['int'],
'preg_match' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'string[]', 'flags='=>'0|', 'offset='=>'int'], 'preg_match' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'string[]', 'flags='=>'0', 'offset='=>'int'],
'preg_match\'1' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], 'preg_match\'1' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_match_all' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], 'preg_match_all' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_quote' => ['string', 'str'=>'string', 'delimiter='=>'string'], 'preg_quote' => ['string', 'str'=>'string', 'delimiter='=>'string'],

View File

@ -14454,7 +14454,7 @@ return [
'preg_filter' => ['null|string|string[]', 'pattern'=>'mixed', 'replacement'=>'mixed', 'subject'=>'mixed', 'limit='=>'int', '&w_count='=>'int'], 'preg_filter' => ['null|string|string[]', 'pattern'=>'mixed', 'replacement'=>'mixed', 'subject'=>'mixed', 'limit='=>'int', '&w_count='=>'int'],
'preg_grep' => ['array|false', 'pattern'=>'string', 'array'=>'array', 'flags='=>'int'], 'preg_grep' => ['array|false', 'pattern'=>'string', 'array'=>'array', 'flags='=>'int'],
'preg_last_error' => ['int'], 'preg_last_error' => ['int'],
'preg_match' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'string[]', 'flags='=>'0|', 'offset='=>'int'], 'preg_match' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'string[]', 'flags='=>'0', 'offset='=>'int'],
'preg_match\'1' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], 'preg_match\'1' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_match_all' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], 'preg_match_all' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_quote' => ['string', 'str'=>'string', 'delimiter='=>'string'], 'preg_quote' => ['string', 'str'=>'string', 'delimiter='=>'string'],

View File

@ -259,14 +259,14 @@ class CodeLocation
} }
if (preg_match($regex, $preview_snippet, $matches, PREG_OFFSET_CAPTURE)) { if (preg_match($regex, $preview_snippet, $matches, PREG_OFFSET_CAPTURE)) {
if (!isset($matches[1]) || (int)$matches[1][1] === -1) { if (!isset($matches[1]) || $matches[1][1] === -1) {
throw new \LogicException( throw new \LogicException(
"Failed to match anything to 1st capturing group, " "Failed to match anything to 1st capturing group, "
. "or regex doesn't contain 1st capturing group, regex type " . $this->regex_type . "or regex doesn't contain 1st capturing group, regex type " . $this->regex_type
); );
} }
$this->selection_start = $this->selection_start + (int)$matches[1][1]; $this->selection_start = $this->selection_start + $matches[1][1];
$this->selection_end = $this->selection_start + strlen((string)$matches[1][0]); $this->selection_end = $this->selection_start + strlen($matches[1][0]);
} }
} }

View File

@ -635,7 +635,7 @@ class ArgumentsAnalyzer
} }
} }
if ($method_id === 'preg_match_all' && count($args) > 3) { if (($method_id === 'preg_match_all' || $method_id === 'preg_match') && count($args) > 3) {
$args = array_reverse($args, true); $args = array_reverse($args, true);
} }

View File

@ -325,7 +325,7 @@ class ClassLikeDocblockParser
$end_of_method_regex = '/(?<!array\()\) ?(\: ?(\??[\\\\a-zA-Z0-9_]+))?/'; $end_of_method_regex = '/(?<!array\()\) ?(\: ?(\??[\\\\a-zA-Z0-9_]+))?/';
if (preg_match($end_of_method_regex, $method_entry, $matches, PREG_OFFSET_CAPTURE)) { if (preg_match($end_of_method_regex, $method_entry, $matches, PREG_OFFSET_CAPTURE)) {
$method_entry = substr($method_entry, 0, (int) $matches[0][1] + strlen((string) $matches[0][0])); $method_entry = substr($method_entry, 0, $matches[0][1] + strlen($matches[0][0]));
} }
$method_entry = str_replace([', ', '( '], [',', '('], $method_entry); $method_entry = str_replace([', ', '( '], [',', '('], $method_entry);

View File

@ -963,6 +963,24 @@ function preg_replace_callback($pattern, $callback, $subject, int $limit = -1, &
*/ */
function preg_match_all($pattern, $subject, &$matches = [], int $flags = 1, int $offset = 0) {} function preg_match_all($pattern, $subject, &$matches = [], int $flags = 1, int $offset = 0) {}
/**
* @psalm-pure
* @template TFlags as int-mask<0, 256, 512>
*
* @param string $pattern
* @param string $subject
* @param mixed $matches
* @param TFlags $flags
* @param-out (TFlags is 256 ? array<array-key, array{string, 0|positive-int}|array{'', -1}> :
* TFlags is 512 ? array<array-key, string|null> :
* TFlags is 768 ? array<array-key, array{string, 0|positive-int}|array{null, -1}> :
* array<array-key, string>
* ) $matches
* @return 1|0|false
* @psalm-ignore-falsable-return
*/
function preg_match($pattern, $subject, &$matches = [], int $flags = 0, int $offset = 0) {}
/** /**
* @psalm-pure * @psalm-pure
* *

View File

@ -1282,6 +1282,13 @@ class FunctionCallTest extends TestCase
takesInt(preg_match("{foo}", "foo"));', takesInt(preg_match("{foo}", "foo"));',
], ],
'pregMatch2' => [
'<?php
$r = preg_match("{foo}", "foo");',
'assertions' => [
'$r===' => '0|1|false',
],
],
'pregMatchWithMatches' => [ 'pregMatchWithMatches' => [
'<?php '<?php
/** @param string[] $matches */ /** @param string[] $matches */
@ -1291,6 +1298,14 @@ class FunctionCallTest extends TestCase
takesMatches($matches);', takesMatches($matches);',
], ],
'pregMatchWithMatches2' => [
'<?php
$r = preg_match("{foo}", "foo", $matches);',
'assertions' => [
'$r===' => '0|1|false',
'$matches===' => 'array<array-key, string>',
],
],
'pregMatchWithOffset' => [ 'pregMatchWithOffset' => [
'<?php '<?php
/** @param string[] $matches */ /** @param string[] $matches */
@ -1300,18 +1315,46 @@ class FunctionCallTest extends TestCase
takesMatches($matches);', takesMatches($matches);',
], ],
'pregMatchWithOffset2' => [
'<?php
$r = preg_match("{foo}", "foo", $matches, 0, 10);',
'assertions' => [
'$r===' => '0|1|false',
'$matches===' => 'array<array-key, string>',
],
],
'pregMatchWithFlags' => [ 'pregMatchWithFlags' => [
'<?php '<?php
function takesInt(int $i) : void {} function takesInt(int $i) : void {}
if (preg_match("{foo}", "this is foo", $matches, PREG_OFFSET_CAPTURE)) { if (preg_match("{foo}", "this is foo", $matches, PREG_OFFSET_CAPTURE)) {
/**
* @psalm-suppress MixedArrayAccess
* @psalm-suppress MixedArgument
*/
takesInt($matches[0][1]); takesInt($matches[0][1]);
}', }',
], ],
'pregMatchWithFlagOffsetCapture' => [
'<?php
$r = preg_match("{foo}", "foo", $matches, PREG_OFFSET_CAPTURE);',
'assertions' => [
'$r===' => '0|1|false',
'$matches===' => 'array<array-key, array{string, int}>',
],
],
'PHP72-pregMatchWithFlagUnmatchedAsNull' => [
'<?php
$r = preg_match("{foo}", "foo", $matches, PREG_UNMATCHED_AS_NULL);',
'assertions' => [
'$r===' => '0|1|false',
'$matches===' => 'array<array-key, null|string>',
],
],
'PHP72-pregMatchWithFlagOffsetCaptureAndUnmatchedAsNull' => [
'<?php
$r = preg_match("{foo}", "foo", $matches, PREG_OFFSET_CAPTURE | PREG_UNMATCHED_AS_NULL);',
'assertions' => [
'$r===' => '0|1|false',
'$matches===' => 'array<array-key, array{null|string, int}>',
],
],
'pregReplaceCallback' => [ 'pregReplaceCallback' => [
'<?php '<?php
function foo(string $s) : string { function foo(string $s) : string {
@ -1449,7 +1492,10 @@ class FunctionCallTest extends TestCase
], ],
'writeArgsAllowed' => [ 'writeArgsAllowed' => [
'<?php '<?php
/** @return false|int */ /**
* @param 0|256|512|768 $flags
* @return false|int
*/
function safeMatch(string $pattern, string $subject, ?array $matches = null, int $flags = 0) { function safeMatch(string $pattern, string $subject, ?array $matches = null, int $flags = 0) {
return \preg_match($pattern, $subject, $matches, $flags); return \preg_match($pattern, $subject, $matches, $flags);
} }

View File

@ -35,14 +35,18 @@ trait ValidCodeAnalysisTestTrait
string $php_version = '7.3' string $php_version = '7.3'
): void { ): void {
$test_name = $this->getTestName(); $test_name = $this->getTestName();
if (strpos($test_name, 'PHP73-') !== false) { if (strpos($test_name, 'PHP71-') !== false) {
if (version_compare(PHP_VERSION, '7.3.0', '<')) {
$this->markTestSkipped('Test case requires PHP 7.3.');
}
} elseif (strpos($test_name, 'PHP71-') !== false) {
if (version_compare(PHP_VERSION, '7.1.0', '<')) { if (version_compare(PHP_VERSION, '7.1.0', '<')) {
$this->markTestSkipped('Test case requires PHP 7.1.'); $this->markTestSkipped('Test case requires PHP 7.1.');
} }
} elseif (strpos($test_name, 'PHP72-') !== false) {
if (version_compare(PHP_VERSION, '7.2.0', '<')) {
$this->markTestSkipped('Test case requires PHP 7.2.');
}
} elseif (strpos($test_name, 'PHP73-') !== false) {
if (version_compare(PHP_VERSION, '7.3.0', '<')) {
$this->markTestSkipped('Test case requires PHP 7.3.');
}
} elseif (strpos($test_name, 'PHP80-') !== false) { } elseif (strpos($test_name, 'PHP80-') !== false) {
if (version_compare(PHP_VERSION, '8.0.0', '<')) { if (version_compare(PHP_VERSION, '8.0.0', '<')) {
$this->markTestSkipped('Test case requires PHP 8.0.'); $this->markTestSkipped('Test case requires PHP 8.0.');