From 4b10905b4041f131d96e51aaef9bb967d6c57133 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 22:22:32 +0200 Subject: [PATCH 01/19] Fix literal-string|non-empty-literal-string --- src/Psalm/Internal/Type/TypeCombiner.php | 9 ++++++++- tests/TypeCombinationTest.php | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index 5e05fea1cff..c37b7eeeb5c 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -1227,9 +1227,16 @@ private static function scrapeStringProperties( ) { $combination->value_types['string'] = new TNonEmptyString(); } elseif (get_class($type) === TNonEmptyNonspecificLiteralString::class - && $combination->value_types['string'] instanceof TNonEmptyString + && ( + $combination->value_types['string'] instanceof TNonEmptyString + || $combination->value_types['string'] instanceof TNonspecificLiteralString + ) ) { // do nothing + } elseif (get_class($type) === TNonspecificLiteralString::class + && get_class($combination->value_types['string']) === TNonEmptyNonspecificLiteralString::class + ) { + $combination->value_types['string'] = $type; } else { $combination->value_types['string'] = new TString(); } diff --git a/tests/TypeCombinationTest.php b/tests/TypeCombinationTest.php index 8cab04fcacd..165ef58b6ea 100644 --- a/tests/TypeCombinationTest.php +++ b/tests/TypeCombinationTest.php @@ -947,6 +947,20 @@ public function providerTestValidTypeCombination(): array 'class-string', ], ], + 'unionNonEmptyLiteralStringAndLiteralString' => [ + 'literal-string', + [ + 'non-empty-literal-string', + 'literal-string', + ], + ], + 'unionLiteralStringAndNonEmptyLiteralString' => [ + 'literal-string', + [ + 'literal-string', + 'non-empty-literal-string', + ], + ], ]; } From 3643ff745cd2e4d35afbb16acdc2837ae703fdb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20B=C3=B6sing?= <2189546+boesing@users.noreply.github.com> Date: Thu, 2 May 2024 14:06:07 +0200 Subject: [PATCH 02/19] redis: add possible types for `Redis#auth` method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com> --- stubs/extensions/redis.phpstub | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stubs/extensions/redis.phpstub b/stubs/extensions/redis.phpstub index d14673868cf..27f28f45011 100644 --- a/stubs/extensions/redis.phpstub +++ b/stubs/extensions/redis.phpstub @@ -36,7 +36,8 @@ class Redis { /** @return false|int|Redis */ public function append(string $key, string $value) {} - public function auth(mixed $credentials): bool {} + /** @param array{string,string}|array{string}|string $credentials */ + public function auth(#[\SensitiveParameter] mixed $credentials): bool {} public function bgSave(): bool {} From 0776d8cf60e41be695106a5b59584423bef4c48f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 3 May 2024 08:05:20 +0200 Subject: [PATCH 03/19] Avoid false positive about non-callable --- .../Statements/Expression/Call/ArgumentAnalyzer.php | 13 +++++++++++++ tests/CallableTest.php | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index 5442bfae87a..7b20cb156b1 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -955,12 +955,25 @@ public static function verifyType( ) { $potential_method_ids = []; + $builder = $param_type->getBuilder(); + $builder->removeType('callable'); + $param_type_without_callable = $builder->freeze(); + foreach ($input_type->getAtomicTypes() as $input_type_part) { if ($input_type_part instanceof TList) { $input_type_part = $input_type_part->getKeyedArray(); } if ($input_type_part instanceof TKeyedArray) { + // If the param accept an array, we don't report arrays as wrong callbacks. + if (UnionTypeComparator::isContainedBy( + $codebase, + $input_type, + $param_type_without_callable, + )) { + continue; + } + $potential_method_id = CallableTypeComparator::getCallableMethodIdFromTKeyedArray( $input_type_part, $codebase, diff --git a/tests/CallableTest.php b/tests/CallableTest.php index 5ca5f06986d..7e3744f0944 100644 --- a/tests/CallableTest.php +++ b/tests/CallableTest.php @@ -2210,6 +2210,15 @@ function foo($arg): void {} foo(["a", "b"]);', ], + 'notCallableArray' => [ + 'code' => ' [ 'code' => ' Date: Fri, 3 May 2024 08:25:12 +0200 Subject: [PATCH 04/19] Fix --- .../Statements/Expression/Call/ArgumentAnalyzer.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index 7b20cb156b1..74a84c94bed 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -955,9 +955,12 @@ public static function verifyType( ) { $potential_method_ids = []; - $builder = $param_type->getBuilder(); - $builder->removeType('callable'); - $param_type_without_callable = $builder->freeze(); + $param_type_without_callable = new Union(array_filter( + $param_type->getAtomicTypes(), + static function (Atomic $atomic) { + return !$atomic instanceof Atomic\TCallableInterface; + }) + ); foreach ($input_type->getAtomicTypes() as $input_type_part) { if ($input_type_part instanceof TList) { From 3b512cedc3af14618da01475df01167bb4164373 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 3 May 2024 08:30:14 +0200 Subject: [PATCH 05/19] Fix --- .../Statements/Expression/Call/ArgumentAnalyzer.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index 74a84c94bed..d577e3c6fa1 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -63,6 +63,7 @@ use Psalm\Type\Union; use UnexpectedValueException; +use function array_filter; use function count; use function explode; use function implode; @@ -955,12 +956,13 @@ public static function verifyType( ) { $potential_method_ids = []; - $param_type_without_callable = new Union(array_filter( + $param_types_without_callable = array_filter( $param_type->getAtomicTypes(), - static function (Atomic $atomic) { - return !$atomic instanceof Atomic\TCallableInterface; - }) + static fn(Atomic $atomic) => !$atomic instanceof Atomic\TCallableInterface, ); + $param_type_without_callable = [] !== $param_types_without_callable + ? new Union($param_types_without_callable) + : null; foreach ($input_type->getAtomicTypes() as $input_type_part) { if ($input_type_part instanceof TList) { @@ -969,7 +971,7 @@ static function (Atomic $atomic) { if ($input_type_part instanceof TKeyedArray) { // If the param accept an array, we don't report arrays as wrong callbacks. - if (UnionTypeComparator::isContainedBy( + if (null !== $param_type_without_callable && UnionTypeComparator::isContainedBy( $codebase, $input_type, $param_type_without_callable, From e97dbab4063d15c8010247c527a8c0d9133ddad0 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 3 May 2024 17:10:29 +0200 Subject: [PATCH 06/19] Feedback --- .../Expression/Call/ArgumentAnalyzer.php | 9 +++++++++ tests/CallableTest.php | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index d577e3c6fa1..2fd7cd1b5a7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -1024,6 +1024,15 @@ public static function verifyType( } elseif ($input_type_part instanceof TLiteralString && strpos($input_type_part->value, '::') ) { + // If the param also accept a string, we don't report string as wrong callbacks. + if (null !== $param_type_without_callable && UnionTypeComparator::isContainedBy( + $codebase, + $input_type, + $param_type_without_callable, + )) { + continue; + } + $parts = explode('::', $input_type_part->value); /** @psalm-suppress PossiblyUndefinedIntArrayOffset */ $potential_method_id = new MethodIdentifier( diff --git a/tests/CallableTest.php b/tests/CallableTest.php index 7e3744f0944..f59707faa35 100644 --- a/tests/CallableTest.php +++ b/tests/CallableTest.php @@ -2219,6 +2219,15 @@ function foo($arg): void {} foo([\DateTime::class, "format"]);', ], + 'notCallableString' => [ + 'code' => ' [ 'code' => ' 'ParentNotFound', ], + 'wrongCallableInUnion' => [ + 'code' => ' 'InvalidArgument', + ], ]; } } From 5bb9ebf8226ecc5924cf7cc2bdc3c6077bc8bad4 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Fri, 3 May 2024 21:43:19 +0200 Subject: [PATCH 07/19] Casting int-range should keep literals Fix https://github.com/vimeo/psalm/issues/10940 500 limit taken from TypeCombiner --- .../Statements/Expression/CastAnalyzer.php | 24 ++++++++++++++++++- tests/CastTest.php | 10 ++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index 5832bb159fe..dbb659f9ff6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -28,6 +28,7 @@ use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TInt; +use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TLiteralFloat; @@ -53,6 +54,7 @@ use function array_pop; use function array_values; use function get_class; +use function range; use function strtolower; /** @@ -537,6 +539,18 @@ public static function castFloatAttempt( continue; } + if ($atomic_type instanceof TIntRange + && $atomic_type->min_bound !== null + && $atomic_type->max_bound !== null + && ($atomic_type->max_bound - $atomic_type->min_bound) < 500 + ) { + foreach (range($atomic_type->min_bound, $atomic_type->max_bound) as $literal_int_value) { + $valid_floats[] = new TLiteralFloat((float) $literal_int_value); + } + + continue; + } + if ($atomic_type instanceof TInt) { if ($atomic_type instanceof TLiteralInt) { $valid_floats[] = new TLiteralFloat((float) $atomic_type->value); @@ -721,9 +735,17 @@ public static function castStringAttempt( || $atomic_type instanceof TNumeric ) { if ($atomic_type instanceof TLiteralInt || $atomic_type instanceof TLiteralFloat) { - $castable_types[] = Type::getAtomicStringFromLiteral((string) $atomic_type->value); + $valid_strings[] = Type::getAtomicStringFromLiteral((string) $atomic_type->value); } elseif ($atomic_type instanceof TNonspecificLiteralInt) { $castable_types[] = new TNonspecificLiteralString(); + } elseif ($atomic_type instanceof TIntRange + && $atomic_type->min_bound !== null + && $atomic_type->max_bound !== null + && ($atomic_type->max_bound - $atomic_type->min_bound) < 500 + ) { + foreach (range($atomic_type->min_bound, $atomic_type->max_bound) as $literal_int_value) { + $valid_strings[] = Type::getAtomicStringFromLiteral((string) $literal_int_value); + } } else { $castable_types[] = new TNumericString(); } diff --git a/tests/CastTest.php b/tests/CastTest.php index fe93e5d7b3f..414aa70aedb 100644 --- a/tests/CastTest.php +++ b/tests/CastTest.php @@ -49,5 +49,15 @@ public function providerValidCodeParse(): iterable '$a===' => 'array{a: int, b: string, ...}', ], ]; + yield 'castIntRangeToString' => [ + 'code' => ' */ + $int_range = 2; + $string = (string) $int_range; + ', + 'assertions' => [ + '$string===' => "'-1'|'-2'|'-3'|'-4'|'-5'|'0'|'1'|'2'|'3'", + ], + ]; } } From 370880706c5909739391d2873ea58d6b6dd83eef Mon Sep 17 00:00:00 2001 From: Jack Worman Date: Sat, 4 May 2024 19:51:48 -0400 Subject: [PATCH 08/19] Fix-GH-10933-And-GH-10951 --- .../Reflector/ClassLikeNodeScanner.php | 7 +++--- tests/MissingClassConstTypeTest.php | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index c8edcf6723e..dd97adf9b80 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -1338,10 +1338,8 @@ private function visitClassConstDeclaration( ); $type_location = null; - $suppressed_issues = []; - if ($var_comment !== null && $var_comment->type !== null) { + if ($var_comment && $var_comment->type !== null) { $const_type = $var_comment->type; - $suppressed_issues = $var_comment->suppressed_issues; if ($var_comment->type_start !== null && $var_comment->type_end !== null @@ -1357,6 +1355,7 @@ private function visitClassConstDeclaration( } else { $const_type = $inferred_type; } + $suppressed_issues = $var_comment ? $var_comment->suppressed_issues : []; $attributes = []; foreach ($stmt->attrGroups as $attr_group) { @@ -1420,8 +1419,8 @@ private function visitClassConstDeclaration( $description, ); - if ($this->codebase->analysis_php_version_id >= 8_03_00 + && !$storage->final && $stmt->type === null ) { IssueBuffer::maybeAdd( diff --git a/tests/MissingClassConstTypeTest.php b/tests/MissingClassConstTypeTest.php index de413050fdb..adabb5ab269 100644 --- a/tests/MissingClassConstTypeTest.php +++ b/tests/MissingClassConstTypeTest.php @@ -27,6 +27,29 @@ class A { 'ignored_issues' => [], 'php_version' => '8.3', ], + 'no type; >= PHP 8.3; but class is final' => [ + 'code' => <<<'PHP' + [], + 'ignored_issues' => [], + 'php_version' => '8.3', + ], + 'no type; >= PHP 8.3; but psalm-suppressed' => [ + 'code' => <<<'PHP' + [], + 'ignored_issues' => [], + 'php_version' => '8.3', + ], 'no type; < PHP 8.3' => [ 'code' => <<<'PHP' Date: Sun, 5 May 2024 15:32:51 +0200 Subject: [PATCH 09/19] qa: remove `SensitiveParameter` from `Redis##auth` stub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I was expecting that it might have a benefit regarding tentative types but it seems that psalm does not use that and thus was suggested to be removed. Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com> --- stubs/extensions/redis.phpstub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/extensions/redis.phpstub b/stubs/extensions/redis.phpstub index 27f28f45011..97fd397b2dd 100644 --- a/stubs/extensions/redis.phpstub +++ b/stubs/extensions/redis.phpstub @@ -37,7 +37,7 @@ class Redis { public function append(string $key, string $value) {} /** @param array{string,string}|array{string}|string $credentials */ - public function auth(#[\SensitiveParameter] mixed $credentials): bool {} + public function auth(mixed $credentials): bool {} public function bgSave(): bool {} From 71a1ae565b7f30d38fa6fcf894d4796bb3ebb5ef Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 9 May 2024 00:09:53 +0200 Subject: [PATCH 10/19] Precise preg_match_all return type --- dictionaries/CallMap.php | 6 +++--- dictionaries/CallMap_historical.php | 6 +++--- stubs/CoreGenericFunctions.phpstub | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index d0bfe5486cb..1a3a74d1e16 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -9418,9 +9418,9 @@ 'preg_filter' => ['string|string[]|null', 'pattern'=>'string|string[]', 'replacement'=>'string|string[]', 'subject'=>'string|string[]', 'limit='=>'int', '&w_count='=>'int'], 'preg_grep' => ['array|false', 'pattern'=>'string', 'array'=>'array', 'flags='=>'int'], 'preg_last_error' => ['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_all' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], +'preg_match' => ['0|1|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'string[]', 'flags='=>'0', 'offset='=>'int'], +'preg_match\'1' => ['0|1|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], +'preg_match_all' => ['0|positive-int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], 'preg_quote' => ['string', 'str'=>'string', 'delimiter='=>'?string'], 'preg_replace' => ['string|string[]|null', 'pattern'=>'string|array', 'replacement'=>'string|array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], 'preg_replace_callback' => ['string|null', 'pattern'=>'string|array', 'callback'=>'callable(string[]):string', 'subject'=>'string', 'limit='=>'int', '&w_count='=>'int', 'flags='=>'int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 37681adfec7..a70759688a9 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -13499,9 +13499,9 @@ 'preg_filter' => ['string|string[]|null', 'pattern'=>'string|string[]', 'replacement'=>'string|string[]', 'subject'=>'string|string[]', 'limit='=>'int', '&w_count='=>'int'], 'preg_grep' => ['array|false', 'pattern'=>'string', 'array'=>'array', 'flags='=>'int'], 'preg_last_error' => ['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_all' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], + 'preg_match' => ['0|1|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'string[]', 'flags='=>'0', 'offset='=>'int'], + 'preg_match\'1' => ['0|1|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], + 'preg_match_all' => ['0|positive-int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], 'preg_quote' => ['string', 'str'=>'string', 'delimiter='=>'string'], 'preg_replace' => ['string|string[]|null', 'pattern'=>'string|array', 'replacement'=>'string|array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], 'preg_replace_callback' => ['string|null', 'pattern'=>'string|array', 'callback'=>'callable(string[]):string', 'subject'=>'string', 'limit='=>'int', '&w_count='=>'int'], diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index e793b13d22b..28cf848e319 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -1249,7 +1249,7 @@ function preg_replace_callback($pattern, $callback, $subject, int $limit = -1, & * ) * ) * ) $matches - * @return int|false + * @return 0|positive-int|false * @psalm-ignore-falsable-return */ function preg_match_all($pattern, $subject, &$matches = [], int $flags = 1, int $offset = 0) {} From a9264997d58c07fdc498447528c99e1781842fdc Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 15 May 2024 17:54:15 +0200 Subject: [PATCH 11/19] Add support for phpstan-pure --- .../Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php index ad3f0a4a0d7..e44c08bfbc7 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php @@ -574,6 +574,7 @@ public static function parse( $info->variadic = isset($parsed_docblock->tags['psalm-variadic']); $info->pure = isset($parsed_docblock->tags['psalm-pure']) + || isset($parsed_docblock->tags['phpstan-pure']) || isset($parsed_docblock->tags['pure']); if (isset($parsed_docblock->tags['psalm-mutation-free'])) { From 58af0ebbb43ca497cdb14e34a1f34240e1beacb2 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 29 May 2024 23:12:49 +0200 Subject: [PATCH 12/19] Use non deprecated notation --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_historical.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 1a3a74d1e16..040a4854229 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -9420,7 +9420,7 @@ 'preg_last_error' => ['int'], 'preg_match' => ['0|1|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'string[]', 'flags='=>'0', 'offset='=>'int'], 'preg_match\'1' => ['0|1|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], -'preg_match_all' => ['0|positive-int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], +'preg_match_all' => ['int<0,max>|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], 'preg_quote' => ['string', 'str'=>'string', 'delimiter='=>'?string'], 'preg_replace' => ['string|string[]|null', 'pattern'=>'string|array', 'replacement'=>'string|array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], 'preg_replace_callback' => ['string|null', 'pattern'=>'string|array', 'callback'=>'callable(string[]):string', 'subject'=>'string', 'limit='=>'int', '&w_count='=>'int', 'flags='=>'int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index a70759688a9..ce9aaa312a6 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -13501,7 +13501,7 @@ 'preg_last_error' => ['int'], 'preg_match' => ['0|1|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'string[]', 'flags='=>'0', 'offset='=>'int'], 'preg_match\'1' => ['0|1|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], - 'preg_match_all' => ['0|positive-int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], + 'preg_match_all' => ['int<0,max>|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'], 'preg_quote' => ['string', 'str'=>'string', 'delimiter='=>'string'], 'preg_replace' => ['string|string[]|null', 'pattern'=>'string|array', 'replacement'=>'string|array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], 'preg_replace_callback' => ['string|null', 'pattern'=>'string|array', 'callback'=>'callable(string[]):string', 'subject'=>'string', 'limit='=>'int', '&w_count='=>'int'], From fbf07dbd3050446a8180c18c6c77e630132352b4 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sun, 2 Jun 2024 12:33:18 +0000 Subject: [PATCH 13/19] up-to-date output-format list --- src/Psalm/Internal/Cli/Psalm.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Cli/Psalm.php b/src/Psalm/Internal/Cli/Psalm.php index 30b6679ecd4..e9156795953 100644 --- a/src/Psalm/Internal/Cli/Psalm.php +++ b/src/Psalm/Internal/Cli/Psalm.php @@ -31,6 +31,7 @@ use Psalm\Progress\VoidProgress; use Psalm\Report; use Psalm\Report\ReportOptions; +use ReflectionClass; use RuntimeException; use Symfony\Component\Filesystem\Path; @@ -67,11 +68,13 @@ use function preg_replace; use function realpath; use function setlocale; +use function sort; use function str_repeat; use function str_replace; use function strlen; use function strpos; use function substr; +use function wordwrap; use const DIRECTORY_SEPARATOR; use const JSON_THROW_ON_ERROR; @@ -87,6 +90,7 @@ require_once __DIR__ . '/../Composer.php'; require_once __DIR__ . '/../IncludeCollector.php'; require_once __DIR__ . '/../../IssueBuffer.php'; +require_once __DIR__ . '/../../Report.php'; /** * @internal @@ -1250,6 +1254,16 @@ private static function generateStubs( */ private static function getHelpText(): string { + $formats = []; + /** @var string $value */ + foreach ((new ReflectionClass(Report::class))->getConstants() as $constant => $value) { + if (strpos($constant, 'TYPE_') === 0) { + $formats[] = $value; + } + } + sort($formats); + $outputFormats = wordwrap(implode(', ', $formats), 75, "\n "); + return << Date: Sun, 2 Jun 2024 13:04:18 +0000 Subject: [PATCH 14/19] up-to-date report list --- .../Internal/Analyzer/ProjectAnalyzer.php | 16 +-------------- src/Psalm/Internal/Cli/Psalm.php | 8 ++++++-- src/Psalm/Report.php | 20 +++++++++++++++++++ 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php index ed22c279d97..d5e9d090dc4 100644 --- a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php @@ -329,21 +329,7 @@ public static function getFileReportOptions(array $report_file_paths, bool $show { $report_options = []; - $mapping = [ - 'checkstyle.xml' => Report::TYPE_CHECKSTYLE, - 'sonarqube.json' => Report::TYPE_SONARQUBE, - 'codeclimate.json' => Report::TYPE_CODECLIMATE, - 'summary.json' => Report::TYPE_JSON_SUMMARY, - 'junit.xml' => Report::TYPE_JUNIT, - '.xml' => Report::TYPE_XML, - '.json' => Report::TYPE_JSON, - '.txt' => Report::TYPE_TEXT, - '.emacs' => Report::TYPE_EMACS, - '.pylint' => Report::TYPE_PYLINT, - '.console' => Report::TYPE_CONSOLE, - '.sarif' => Report::TYPE_SARIF, - 'count.txt' => Report::TYPE_COUNT, - ]; + $mapping = Report::getMapping(); foreach ($report_file_paths as $report_file_path) { foreach ($mapping as $extension => $type) { diff --git a/src/Psalm/Internal/Cli/Psalm.php b/src/Psalm/Internal/Cli/Psalm.php index e9156795953..dcfb903787e 100644 --- a/src/Psalm/Internal/Cli/Psalm.php +++ b/src/Psalm/Internal/Cli/Psalm.php @@ -37,6 +37,7 @@ use function array_filter; use function array_key_exists; +use function array_keys; use function array_map; use function array_merge; use function array_slice; @@ -1264,6 +1265,10 @@ private static function getHelpText(): string sort($formats); $outputFormats = wordwrap(implode(', ', $formats), 75, "\n "); + $reports = array_keys(Report::getMapping()); + sort($reports); + $reportFormats = wordwrap('"' . implode('", "', $reports) . '"', 75, "\n "); + return << self::TYPE_CHECKSTYLE, + 'sonarqube.json' => self::TYPE_SONARQUBE, + 'codeclimate.json' => self::TYPE_CODECLIMATE, + 'summary.json' => self::TYPE_JSON_SUMMARY, + 'junit.xml' => self::TYPE_JUNIT, + '.xml' => self::TYPE_XML, + '.sarif.json' => self::TYPE_SARIF, + '.json' => self::TYPE_JSON, + '.txt' => self::TYPE_TEXT, + '.emacs' => self::TYPE_EMACS, + '.pylint' => self::TYPE_PYLINT, + '.console' => self::TYPE_CONSOLE, + '.sarif' => self::TYPE_SARIF, + 'count.txt' => self::TYPE_COUNT, + ]; + } } From bce59cf5ad31fe737d58729196f6c0ac134d2bff Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sun, 2 Jun 2024 13:34:36 +0000 Subject: [PATCH 15/19] fix psalm errors --- src/Psalm/Internal/Cli/Psalm.php | 1 + src/Psalm/Report.php | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/Psalm/Internal/Cli/Psalm.php b/src/Psalm/Internal/Cli/Psalm.php index dcfb903787e..99d87b52269 100644 --- a/src/Psalm/Internal/Cli/Psalm.php +++ b/src/Psalm/Internal/Cli/Psalm.php @@ -1265,6 +1265,7 @@ private static function getHelpText(): string sort($formats); $outputFormats = wordwrap(implode(', ', $formats), 75, "\n "); + /** @psalm-suppress ImpureMethodCall */ $reports = array_keys(Report::getMapping()); sort($reports); $reportFormats = wordwrap('"' . implode('", "', $reports) . '"', 75, "\n "); diff --git a/src/Psalm/Report.php b/src/Psalm/Report.php index f60483d82ec..3ed578c06cf 100644 --- a/src/Psalm/Report.php +++ b/src/Psalm/Report.php @@ -98,6 +98,9 @@ protected function xmlEncode(string $data): string abstract public function create(): string; + /** + * @return array + */ public static function getMapping(): array { return [ From 22379c751ce64df065b1e8e053f3c75d4293211e Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 8 Jun 2024 13:18:02 +0530 Subject: [PATCH 16/19] Fix signature of Locale::canonicalize. The method can return null, https://www.php.net/manual/en/locale.canonicalize.php. --- dictionaries/CallMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index d0bfe5486cb..210571b5dff 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -6446,7 +6446,7 @@ 'litespeed_request_headers' => ['array'], 'litespeed_response_headers' => ['array'], 'Locale::acceptFromHttp' => ['string|false', 'header'=>'string'], -'Locale::canonicalize' => ['string', 'locale'=>'string'], +'Locale::canonicalize' => ['?string', 'locale'=>'string'], 'Locale::composeLocale' => ['string', 'subtags'=>'array'], 'Locale::filterMatches' => ['?bool', 'languageTag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'], 'Locale::getAllVariants' => ['array', 'locale'=>'string'], From c8a0d5e665df83bf986e0f58ae253583a231af1f Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 10 Jun 2024 11:13:58 +0530 Subject: [PATCH 17/19] Fix signature for Locale::canonicalize() --- dictionaries/CallMap_historical.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 37681adfec7..f87f7759846 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -3393,7 +3393,7 @@ 'LimitIterator::seek' => ['int', 'offset'=>'int'], 'LimitIterator::valid' => ['bool'], 'Locale::acceptFromHttp' => ['string|false', 'header'=>'string'], - 'Locale::canonicalize' => ['string', 'locale'=>'string'], + 'Locale::canonicalize' => ['?string', 'locale'=>'string'], 'Locale::composeLocale' => ['string', 'subtags'=>'array'], 'Locale::filterMatches' => ['?bool', 'languageTag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'], 'Locale::getAllVariants' => ['array', 'locale'=>'string'], From 626efd51073d46382c0108439321cc2f4721341d Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 10 Jun 2024 19:38:50 +0530 Subject: [PATCH 18/19] Update test --- tests/Internal/Codebase/InternalCallMapHandlerTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Internal/Codebase/InternalCallMapHandlerTest.php b/tests/Internal/Codebase/InternalCallMapHandlerTest.php index 82909f86027..e5f6320fba5 100644 --- a/tests/Internal/Codebase/InternalCallMapHandlerTest.php +++ b/tests/Internal/Codebase/InternalCallMapHandlerTest.php @@ -198,7 +198,6 @@ class InternalCallMapHandlerTest extends TestCase 'infiniteiterator::getinneriterator' => ['8.1', '8.2', '8.3'], 'iteratoriterator::getinneriterator' => ['8.1', '8.2', '8.3'], 'limititerator::getinneriterator' => ['8.1', '8.2', '8.3'], - 'locale::canonicalize' => ['8.1', '8.2', '8.3'], 'locale::getallvariants' => ['8.1', '8.2', '8.3'], 'locale::getkeywords' => ['8.1', '8.2', '8.3'], 'locale::getprimarylanguage' => ['8.1', '8.2', '8.3'], From 8af77f0af3d1b6802464906be1746f176c2471dd Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 16 Jun 2024 14:20:43 +0200 Subject: [PATCH 19/19] Feedback --- stubs/CoreGenericFunctions.phpstub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index 28cf848e319..9f82c9a335f 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -1249,7 +1249,7 @@ function preg_replace_callback($pattern, $callback, $subject, int $limit = -1, & * ) * ) * ) $matches - * @return 0|positive-int|false + * @return int<0,max>|false * @psalm-ignore-falsable-return */ function preg_match_all($pattern, $subject, &$matches = [], int $flags = 1, int $offset = 0) {}