From ea39a6e6742eaf4c5af6e09dc477068f8af9dba7 Mon Sep 17 00:00:00 2001 From: Greg Hargreaves Date: Mon, 17 Oct 2022 00:33:26 +0100 Subject: [PATCH 01/41] Fixes #7810 --- stubs/Php80.phpstub | 2 ++ stubs/Reflection.phpstub | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/stubs/Php80.phpstub b/stubs/Php80.phpstub index 001ebf17ae6..50d9d78e4be 100644 --- a/stubs/Php80.phpstub +++ b/stubs/Php80.phpstub @@ -57,6 +57,8 @@ class ReflectionClassConstant class Attribute { + public int $flags; + public const TARGET_CLASS = 1; public const TARGET_FUNCTION = 2; public const TARGET_METHOD = 4; diff --git a/stubs/Reflection.phpstub b/stubs/Reflection.phpstub index cfbe3ba2375..40dfbe9ba3c 100644 --- a/stubs/Reflection.phpstub +++ b/stubs/Reflection.phpstub @@ -88,6 +88,16 @@ class ReflectionProperty implements Reflector * @psalm-mutation-free */ public function getType() : ?ReflectionType {} + + /** + * @since 8.0 + */ + public function hasDefaultValue(): bool {} + + /** + * @since 8.0 + */ + public function isPromoted(): bool {} } class ReflectionMethod implements Reflector @@ -132,6 +142,11 @@ class ReflectionParameter implements Reflector { * @return ($name is null ? array> : array>) */ public function getAttributes(?string $name = null, int $flags = 0): array {} + + /** + * @since 8.0 + */ + public function isPromoted(): bool {} } /** From 45bb58ebfac386e608af34eb28fe2add99fbce25 Mon Sep 17 00:00:00 2001 From: Greg Hargreaves Date: Mon, 17 Oct 2022 00:46:19 +0100 Subject: [PATCH 02/41] Add stub for reflection property isReadOnly --- stubs/Reflection.phpstub | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/stubs/Reflection.phpstub b/stubs/Reflection.phpstub index 40dfbe9ba3c..08e2a3deaf8 100644 --- a/stubs/Reflection.phpstub +++ b/stubs/Reflection.phpstub @@ -98,6 +98,11 @@ class ReflectionProperty implements Reflector * @since 8.0 */ public function isPromoted(): bool {} + + /** + * @since 8.1 + */ + public function isReadOnly(): bool {} } class ReflectionMethod implements Reflector From 25b01ce3a3e0602e5dd60432f480c4e829bf79a7 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Mon, 17 Oct 2022 15:36:08 +0200 Subject: [PATCH 03/41] ensure callbacks have the required number of params Fix https://github.com/vimeo/psalm/issues/8593 --- .../Type/Comparator/UnionTypeComparator.php | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php b/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php index c997199d650..1906fc938f1 100644 --- a/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php @@ -6,6 +6,7 @@ use Psalm\Internal\Type\TypeExpander; use Psalm\Type\Atomic; use Psalm\Type\Atomic\TArrayKey; +use Psalm\Type\Atomic\TCallable; use Psalm\Type\Atomic\TClassConstant; use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TIntRange; @@ -21,6 +22,10 @@ use function array_pop; use function array_push; use function array_reverse; +use function count; +use function is_array; + +use const PHP_INT_MAX; /** * @internal @@ -134,6 +139,39 @@ public static function isContainedBy( continue; } + // if params are specified + if ($container_type_part instanceof TCallable + && is_array($container_type_part->params) + && $input_type_part instanceof TCallable + ) { + $container_required_param_count = 0; + foreach ($container_type_part->params as $index => $container_param) { + if ($container_param->is_optional === false) { + $container_required_param_count = $index + 1; + } + } + + $input_required_param_count = 0; + if (!is_array($input_type_part->params)) { + // it's not declared, there can be an arbitrary number of params + $input_all_param_count = PHP_INT_MAX; + } else { + $input_all_param_count = count($input_type_part->params); + foreach ($input_type_part->params as $index => $input_param) { + if ($input_param->is_optional === false) { + $input_required_param_count = $index + 1; + } + } + } + + // too few or too many non-optional params provided in callback + if ($container_required_param_count > $input_all_param_count + || count($container_type_part->params) < $input_required_param_count + ) { + return false; + } + } + if ($union_comparison_result) { $atomic_comparison_result = new TypeComparisonResult(); } else { From 2c47d06968090a4682454a7522aa5c8543394714 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Mon, 17 Oct 2022 16:25:43 +0200 Subject: [PATCH 04/41] fix tests --- tests/Template/FunctionTemplateTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Template/FunctionTemplateTest.php b/tests/Template/FunctionTemplateTest.php index 407e96ebe5c..70b99124934 100644 --- a/tests/Template/FunctionTemplateTest.php +++ b/tests/Template/FunctionTemplateTest.php @@ -1560,13 +1560,13 @@ function deserialize_object(string $data, string $type) {}' * @template TNewKey of array-key * @template TNewValue * @psalm-param iterable $iterable - * @psalm-param callable(TKey, TValue): iterable $mapper + * @psalm-param callable(TKey): iterable $mapper * @psalm-return \Generator */ function map(iterable $iterable, callable $mapper): Generator { - foreach ($iterable as $key => $value) { - yield from $mapper($key, $value); + foreach ($iterable as $key => $_) { + yield from $mapper($key); } } From 7f35bff0d94feab632dbe8619a10c6b2d86bdb08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20B=C3=B6sing?= <2189546+boesing@users.noreply.github.com> Date: Fri, 14 Oct 2022 00:58:12 +0200 Subject: [PATCH 05/41] feature: enhance type detection for internal php functions `key`, `current`, `end` and `reset` 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> --- ...rayPointerAdjustmentReturnTypeProvider.php | 17 +++- stubs/CoreGenericFunctions.phpstub | 42 +++++++++- tests/ArrayFunctionCallTest.php | 82 ++++++++++++++++++- 3 files changed, 137 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php index 66d39525603..01ba45116e1 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php @@ -19,9 +19,19 @@ use function array_merge; use function array_shift; +use function in_array; class ArrayPointerAdjustmentReturnTypeProvider implements FunctionReturnTypeProviderInterface { + /** + * These functions are already handled by the CoreGenericFunctions stub + */ + const IGNORE_FUNCTION_IDS_FOR_FALSE_RETURN_TYPE = [ + 'reset', + 'end', + 'current', + ]; + /** * @return array */ @@ -82,7 +92,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev if ($value_type->isEmpty()) { $value_type = Type::getFalse(); - } elseif (($function_id !== 'reset' && $function_id !== 'end') || !$definitely_has_items) { + } elseif (!$definitely_has_items || self::isFunctionAlreadyHandledByStub($function_id)) { $value_type->addType(new TFalse); $codebase = $statements_source->getCodebase(); @@ -102,4 +112,9 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev return $value_type; } + + private static function isFunctionAlreadyHandledByStub(string $function_id): bool + { + return !in_array($function_id, self::IGNORE_FUNCTION_IDS_FOR_FALSE_RETURN_TYPE, true); + } } diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index 6cc5eec57a4..adacf7e776c 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -136,7 +136,7 @@ function array_flip(array $array) * * @param TArray $array * - * @return (TArray is array ? null : TKey|null) + * @return (TArray is array ? null : (TArray is non-empty-list ? int<0,max> : (TArray is non-empty-array ? TKey : TKey|null))) * @psalm-pure * @psalm-ignore-nullable-return */ @@ -144,6 +144,46 @@ function key($array) { } +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * @psalm-template TArray as array + * + * @param TArray $array + * + * @return (TArray is array ? false : (TArray is non-empty-array ? TValue : TValue|false)) + * @psalm-pure + */ +function current($array) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * @psalm-template TArray as array + * + * @param TArray $array + * + * @return (TArray is array ? false : (TArray is non-empty-array ? TValue : TValue|false)) + */ +function reset(&$array) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * @psalm-template TArray as array + * + * @param TArray $array + * + * @return (TArray is array ? false : (TArray is non-empty-array ? TValue : TValue|false)) + */ +function end(&$array) +{ +} + /** * @psalm-template TKey as array-key * @psalm-template TArray as array diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index 9a3baee55d1..0b116489f53 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -1085,7 +1085,7 @@ class Foo { $a = ["one" => 1, "two" => 3]; $b = key($a);', 'assertions' => [ - '$b' => 'null|string', + '$b' => 'string', ], ], 'keyEmptyArray' => [ @@ -1100,12 +1100,90 @@ class Foo { ' [ + ' 1, "two" => 3]; + $b = current($a);', + 'assertions' => [ + '$b' => 'int', + ], + ], + 'currentEmptyArray' => [ + ' [ + '$b' => 'false', + ], + ], + 'currentNonEmptyArray' => [ + ' $arr + * @return int + */ + function foo(array $arr) { + return current($arr); + }', + ], + 'reset' => [ + ' 1, "two" => 3]; + $b = reset($a);', + 'assertions' => [ + '$b' => 'int', + ], + ], + 'resetEmptyArray' => [ + ' [ + '$b' => 'false', + ], + ], + 'resetNonEmptyArray' => [ + ' $arr + * @return int + */ + function foo(array $arr) { + return reset($arr); + }', + ], + 'end' => [ + ' 1, "two" => 3]; + $b = end($a);', + 'assertions' => [ + '$b' => 'int', + ], + ], + 'endEmptyArray' => [ + ' [ + '$b' => 'false', + ], + ], + 'endNonEmptyArray' => [ + ' $arr + * @return int + */ + function foo(array $arr) { + return end($arr); + }', + ], 'arrayKeyFirst' => [ ' */ From eb6bbfb3b55ab6b9bbc653060f0d169a5146a17e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20B=C3=B6sing?= <2189546+boesing@users.noreply.github.com> Date: Mon, 17 Oct 2022 20:38:34 +0200 Subject: [PATCH 06/41] qa: remove redundant check as `!empty` already ensures that `key` does not return `null` 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> --- tests/TypeReconciliation/EmptyTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/TypeReconciliation/EmptyTest.php b/tests/TypeReconciliation/EmptyTest.php index 873d75c5459..71ad4d69b89 100644 --- a/tests/TypeReconciliation/EmptyTest.php +++ b/tests/TypeReconciliation/EmptyTest.php @@ -259,7 +259,6 @@ function contains(array $data, array $needle): bool { while (!empty($needle)) { $key = key($needle); - if ($key === null) continue; $val = $needle[$key]; unset($needle[$key]); From 41fbb14586d8b0feac63c13b94888eb030028576 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Mon, 17 Oct 2022 23:05:50 +0200 Subject: [PATCH 07/41] fix wrong php version id in $_FILES --- .../Statements/Expression/Fetch/VariableFetchAnalyzer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index d268f021994..2d7f5bca7cc 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -760,7 +760,7 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ ]), ]; - if ($codebase_analysis_php_version_id >= 81000) { + if ($codebase_analysis_php_version_id >= 80100) { $values['full_path'] = new Union([ new TString(), new TNonEmptyList(Type::getString()), From edeff148b41592bf1731de9e916ea02190773578 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sat, 22 Oct 2022 08:28:56 +0200 Subject: [PATCH 08/41] handle variadic --- .../Internal/Type/Comparator/UnionTypeComparator.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php b/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php index 1906fc938f1..52ad18757f4 100644 --- a/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php @@ -144,11 +144,16 @@ public static function isContainedBy( && is_array($container_type_part->params) && $input_type_part instanceof TCallable ) { + $container_all_param_count = count($container_type_part->params); $container_required_param_count = 0; foreach ($container_type_part->params as $index => $container_param) { if ($container_param->is_optional === false) { $container_required_param_count = $index + 1; } + + if ($container_param->is_variadic === true) { + $container_all_param_count = PHP_INT_MAX; + } } $input_required_param_count = 0; @@ -161,12 +166,16 @@ public static function isContainedBy( if ($input_param->is_optional === false) { $input_required_param_count = $index + 1; } + + if ($input_param->is_variadic === true) { + $input_all_param_count = PHP_INT_MAX; + } } } // too few or too many non-optional params provided in callback if ($container_required_param_count > $input_all_param_count - || count($container_type_part->params) < $input_required_param_count + || $container_all_param_count < $input_required_param_count ) { return false; } From 7d953dfb90d3508039b2ce355744b38710de9d86 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sat, 22 Oct 2022 08:48:18 +0200 Subject: [PATCH 09/41] add tests --- tests/ArgTest.php | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/ArgTest.php b/tests/ArgTest.php index de57435f0e9..2d16c1934dd 100644 --- a/tests/ArgTest.php +++ b/tests/ArgTest.php @@ -312,6 +312,40 @@ public function foo(int ...$values): array } ', ], + 'variadicCallbackArgsCountMatch' => [ + ' [ + ' 'TooFewArguments', ], + 'callbackArgsCountMismatch' => [ + ' 'InvalidScalarArgument', + ], + 'callableArgsCountMismatch' => [ + ' 'InvalidScalarArgument', + ], ]; } } From 6cf0657c6a8d9e8964792c4b741531dffc394ae6 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Mon, 31 Oct 2022 00:43:40 +0300 Subject: [PATCH 10/41] Add dictionary delta for PHP 8.2 --- dictionaries/CallMap_82_delta.php | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 dictionaries/CallMap_82_delta.php diff --git a/dictionaries/CallMap_82_delta.php b/dictionaries/CallMap_82_delta.php new file mode 100644 index 00000000000..30e5e9c6e92 --- /dev/null +++ b/dictionaries/CallMap_82_delta.php @@ -0,0 +1,33 @@ + [ + 'mysqli_execute_query' => ['mysqli_result|bool', 'mysqli'=>'mysqli', 'query'=>'non-empty-string', 'params='=>'list|null'], + 'mysqli::execute_query' => ['mysqli_result|bool', 'query'=>'non-empty-string', 'params='=>'list|null'], + 'openssl_cipher_key_length' => ['positive-int|false', 'cipher_algo'=>'non-empty-string'], + 'curl_upkeep' => ['bool', 'handle'=>'CurlHandle'], + 'ini_parse_quantity' => ['int', 'shorthand'=>'non-empty-string'], + 'memory_reset_peak_usage' => ['void'], + ], + + 'changed' => [ + 'str_split' => [ + 'old' => ['non-empty-list', 'string'=>'string', 'length='=>'positive-int'], + 'new' => ['list', 'string'=>'string', 'length='=>'positive-int'], + ], + ], +]; From 61e95f948d1172b95898a93b9d3d62a583be479d Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Mon, 31 Oct 2022 01:42:05 +0300 Subject: [PATCH 11/41] Add `removed` section --- dictionaries/CallMap_82_delta.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dictionaries/CallMap_82_delta.php b/dictionaries/CallMap_82_delta.php index 30e5e9c6e92..ecbff9d3972 100644 --- a/dictionaries/CallMap_82_delta.php +++ b/dictionaries/CallMap_82_delta.php @@ -30,4 +30,7 @@ 'new' => ['list', 'string'=>'string', 'length='=>'positive-int'], ], ], + + 'removed' => [ + ], ]; From 855eef9205f331259f66e05b355d39b7bfa810f1 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Mon, 31 Oct 2022 12:44:08 +0300 Subject: [PATCH 12/41] Increment PHP_MINOR_VERSION (to load delta for PHP 8.2) --- src/Psalm/Internal/Codebase/InternalCallMapHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Codebase/InternalCallMapHandler.php b/src/Psalm/Internal/Codebase/InternalCallMapHandler.php index 8af4d0cbd75..2e09fbd70b1 100644 --- a/src/Psalm/Internal/Codebase/InternalCallMapHandler.php +++ b/src/Psalm/Internal/Codebase/InternalCallMapHandler.php @@ -37,7 +37,7 @@ class InternalCallMapHandler { private const PHP_MAJOR_VERSION = 8; - private const PHP_MINOR_VERSION = 1; + private const PHP_MINOR_VERSION = 2; private const LOWEST_AVAILABLE_DELTA = 71; /** From b450eac812547cefc52fc2b62b1382acb1261365 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Mon, 31 Oct 2022 13:00:11 +0300 Subject: [PATCH 13/41] Add PHP 8.2 functions to the main delta --- dictionaries/CallMap.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 5284fd27ea7..8b95e378336 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -2,7 +2,7 @@ namespace Phan\Language\Internal; /** - * CURRENT PHP TARGET VERSION: 8.1 + * CURRENT PHP TARGET VERSION: 8.2 * The version above has to match Psalm\Internal\Codebase\InternalCallMapHandler::PHP_(MAJOR|MINOR)_VERSION * * Format @@ -1695,6 +1695,7 @@ 'curl_share_setopt' => ['bool', 'share_handle'=>'CurlShareHandle', 'option'=>'int', 'value'=>'mixed'], 'curl_share_strerror' => ['?string', 'error_code'=>'int'], 'curl_strerror' => ['?string', 'error_code'=>'int'], +'curl_upkeep' => ['bool', 'handle'=>'CurlHandle'], 'curl_unescape' => ['string|false', 'handle'=>'CurlShareHandle', 'string'=>'string'], 'curl_version' => ['array', 'version='=>'int'], 'CURLFile::__construct' => ['void', 'filename'=>'string', 'mimetype='=>'string', 'postfilename='=>'string'], @@ -6136,6 +6137,7 @@ 'ini_get' => ['string|false', 'option'=>'string'], 'ini_get_all' => ['array|false', 'extension='=>'?string', 'details='=>'bool'], 'ini_restore' => ['void', 'option'=>'string'], +'ini_parse_quantity' => ['int', 'shorthand'=>'non-empty-string'], 'ini_set' => ['string|false', 'option'=>'string', 'value'=>'string|int|float|bool|null'], 'inotify_add_watch' => ['int', 'inotify_instance'=>'resource', 'pathname'=>'string', 'mask'=>'int'], 'inotify_init' => ['resource|false'], @@ -7474,6 +7476,7 @@ 'MemcachePool::setServerParams' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'int', 'retry_interval='=>'int', 'status='=>'bool', 'failure_callback='=>'?callable'], 'memory_get_peak_usage' => ['int', 'real_usage='=>'bool'], 'memory_get_usage' => ['int', 'real_usage='=>'bool'], +'memory_reset_peak_usage' => ['void'], 'MessageFormatter::__construct' => ['void', 'locale'=>'string', 'pattern'=>'string'], 'MessageFormatter::create' => ['MessageFormatter', 'locale'=>'string', 'pattern'=>'string'], 'MessageFormatter::format' => ['false|string', 'args'=>'array'], @@ -8442,6 +8445,7 @@ 'mysqli::disable_reads_from_master' => ['bool'], 'mysqli::dump_debug_info' => ['bool'], 'mysqli::escape_string' => ['string', 'string'=>'string'], +'mysqli::execute_query' => ['mysqli_result|bool', 'query'=>'non-empty-string', 'params='=>'list|null'], 'mysqli::get_charset' => ['object'], 'mysqli::get_client_info' => ['string'], 'mysqli::get_connection_stats' => ['array'], @@ -8503,6 +8507,7 @@ 'mysqli_error_list' => ['array', 'mysql'=>'mysqli'], 'mysqli_escape_string' => ['string', 'mysql'=>'mysqli', 'string'=>'string'], 'mysqli_execute' => ['bool', 'statement'=>'mysqli_stmt', 'params='=>'list|null'], +'mysqli_execute_query' => ['mysqli_result|bool', 'mysqli'=>'mysqli', 'query'=>'non-empty-string', 'params='=>'list|null'], 'mysqli_fetch_all' => ['list>', 'result'=>'mysqli_result', 'mode='=>'3'], 'mysqli_fetch_all\'1' => ['list>', 'result'=>'mysqli_result', 'mode='=>'1'], 'mysqli_fetch_all\'2' => ['list>', 'result'=>'mysqli_result', 'mode='=>'2'], @@ -9353,6 +9358,7 @@ 'opendir' => ['resource|false', 'directory'=>'string', 'context='=>'resource'], 'openlog' => ['bool', 'prefix'=>'string', 'flags'=>'int', 'facility'=>'int'], 'openssl_cipher_iv_length' => ['int|false', 'cipher_algo'=>'string'], +'openssl_cipher_key_length' => ['positive-int|false', 'cipher_algo'=>'non-empty-string'], 'openssl_csr_export' => ['bool', 'csr'=>'OpenSSLCertificateSigningRequest|string', '&w_output'=>'string', 'no_text='=>'bool'], 'openssl_csr_export_to_file' => ['bool', 'csr'=>'OpenSSLCertificateSigningRequest|string', 'output_filename'=>'string', 'no_text='=>'bool'], 'openssl_csr_get_public_key' => ['OpenSSLAsymmetricKey|false', 'csr'=>'OpenSSLCertificateSigningRequest|string', 'short_names='=>'bool'], From 77b3962b94cf6beb27deb802683ba4c872f79f01 Mon Sep 17 00:00:00 2001 From: "William Owen O. Ponce" Date: Mon, 31 Oct 2022 23:09:07 +0800 Subject: [PATCH 14/41] Fix typo --- stubs/CoreGenericIterators.phpstub | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stubs/CoreGenericIterators.phpstub b/stubs/CoreGenericIterators.phpstub index c20e54de882..beee41f8b0c 100644 --- a/stubs/CoreGenericIterators.phpstub +++ b/stubs/CoreGenericIterators.phpstub @@ -353,7 +353,7 @@ class CachingIterator extends IteratorIterator implements OuterIterator , ArrayA const TOSTRING_USE_CURRENT = 4 ; const TOSTRING_USE_INNER = 8 ; const FULL_CACHE = 256 ; - + /** * @param Iterator $iterator * @param int-mask-of $flags @@ -561,7 +561,7 @@ class LimitIterator extends IteratorIterator implements OuterIterator { /** * @param Iterator $iterator */ - public function __construct(Iterator $iterator, int $offset = 0, int $count = -1) {} + public function __construct(Iterator $iterator, int $offset = 0, int $limit = -1) {} /** * @return TValue|null current value or null when iterator is drained From 9516baced686a07267179ecf4194aeaa9fa93039 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Mon, 31 Oct 2022 21:27:46 +0300 Subject: [PATCH 15/41] Update `str_split` signature to reflect PHP 8.2 changes --- dictionaries/CallMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 8b95e378336..2eb740c9f69 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -13824,7 +13824,7 @@ 'str_replace' => ['string|string[]', 'search'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', '&w_count='=>'int'], 'str_rot13' => ['string', 'string'=>'string'], 'str_shuffle' => ['string', 'string'=>'string'], -'str_split' => ['non-empty-list', 'string'=>'string', 'length='=>'positive-int'], +'str_split' => ['list', 'string'=>'string', 'length='=>'positive-int'], 'str_starts_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], 'str_word_count' => ['array|int', 'string'=>'string', 'format='=>'int', 'characters='=>'string'], 'strcasecmp' => ['int', 'string1'=>'string', 'string2'=>'string'], From ee7fc0fad88326047c7074f52416cf687428abc4 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 1 Nov 2022 13:26:41 +0100 Subject: [PATCH 16/41] Fix XMLReader::expand() nullable parameter $baseNode --- 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 2eb740c9f69..cf2acbc252a 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -15676,7 +15676,7 @@ 'XMLDiff\Memory::diff' => ['string', 'from'=>'string', 'to'=>'string'], 'XMLDiff\Memory::merge' => ['string', 'src'=>'string', 'diff'=>'string'], 'XMLReader::close' => ['bool'], -'XMLReader::expand' => ['DOMNode|false', 'basenode='=>'DOMNode'], +'XMLReader::expand' => ['DOMNode|false', 'baseNode='=>'?DOMNode'], 'XMLReader::getAttribute' => ['?string', 'name'=>'string'], 'XMLReader::getAttributeNo' => ['?string', 'index'=>'int'], 'XMLReader::getAttributeNs' => ['?string', 'name'=>'string', 'namespaceuri'=>'string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 21df4530590..290bf708754 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -8497,7 +8497,7 @@ 'XMLDiff\Memory::merge' => ['string', 'src'=>'string', 'diff'=>'string'], 'XMLReader::XML' => ['bool', 'source'=>'string', 'encoding='=>'?string', 'options='=>'int'], 'XMLReader::close' => ['bool'], - 'XMLReader::expand' => ['DOMNode|false', 'basenode='=>'DOMNode'], + 'XMLReader::expand' => ['DOMNode|false', 'baseNode='=>'?DOMNode'], 'XMLReader::getAttribute' => ['?string', 'name'=>'string'], 'XMLReader::getAttributeNo' => ['?string', 'index'=>'int'], 'XMLReader::getAttributeNs' => ['?string', 'name'=>'string', 'namespaceuri'=>'string'], From 933caa8f69399037679632934f20e8b98b7718fa Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Tue, 1 Nov 2022 16:25:03 +0100 Subject: [PATCH 17/41] Allow to set PHP 8.2 --- src/Psalm/Internal/Analyzer/ProjectAnalyzer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php index 21cc4ca0893..dd6dc3e81dd 100644 --- a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php @@ -1295,7 +1295,7 @@ public function refactorCodeAfterCompletion(array $to_refactor): void */ public function setPhpVersion(string $version, string $source): void { - if (!preg_match('/^(5\.[456]|7\.[01234]|8\.[01])(\..*)?$/', $version)) { + if (!preg_match('/^(5\.[456]|7\.[01234]|8\.[012])(\..*)?$/', $version)) { throw new UnexpectedValueException('Expecting a version number in the format x.y'); } From ef0a17ee117525b99b56b4b9aa7719f7858c6cce Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 2 Nov 2022 22:52:42 +0100 Subject: [PATCH 18/41] Do not report serialize as unused --- src/Psalm/Internal/Codebase/Functions.php | 3 +++ tests/UnusedCodeTest.php | 25 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/Psalm/Internal/Codebase/Functions.php b/src/Psalm/Internal/Codebase/Functions.php index a05723d923f..1a2f5515fbe 100644 --- a/src/Psalm/Internal/Codebase/Functions.php +++ b/src/Psalm/Internal/Codebase/Functions.php @@ -518,6 +518,9 @@ public function isCallMapFunctionPure( //gettext 'bindtextdomain', + + // serialize + 'serialize', 'unserialize' ]; if (in_array(strtolower($function_id), $impure_functions, true)) { diff --git a/tests/UnusedCodeTest.php b/tests/UnusedCodeTest.php index 341de7fe61c..373ad1cf72b 100644 --- a/tests/UnusedCodeTest.php +++ b/tests/UnusedCodeTest.php @@ -737,6 +737,31 @@ public function unserialize($_serialized) : void {} new Foo();' ], + 'ignoreSerializeAndUnserialize' => [ + ' [ ' Date: Wed, 2 Nov 2022 23:08:03 +0100 Subject: [PATCH 19/41] Mark serialize as impure only for object --- src/Psalm/Internal/Codebase/Functions.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Codebase/Functions.php b/src/Psalm/Internal/Codebase/Functions.php index 1a2f5515fbe..eb73c2932be 100644 --- a/src/Psalm/Internal/Codebase/Functions.php +++ b/src/Psalm/Internal/Codebase/Functions.php @@ -519,14 +519,26 @@ public function isCallMapFunctionPure( //gettext 'bindtextdomain', - // serialize - 'serialize', 'unserialize' + // unserialize + 'unserialize' ]; if (in_array(strtolower($function_id), $impure_functions, true)) { return false; } + if ($function_id === 'serialize' && isset($args[0]) && $type_provider) { + $serialize_type = $type_provider->getType($args[0]->value); + + if ($serialize_type) { + foreach ($serialize_type->getAtomicTypes() as $atomic_serialize_type) { + if ($atomic_serialize_type->isObjectType()) { + return false; + } + } + } + } + if (strpos($function_id, 'image') === 0) { return false; } From 4a273221443fb7d9bdf806fe2e48b323e3a06777 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 3 Nov 2022 11:25:24 +0100 Subject: [PATCH 20/41] serialize is not pure for array of object --- src/Psalm/Internal/Codebase/Functions.php | 8 ++--- .../TypeVisitor/ContainsObjectTypeVisitor.php | 36 +++++++++++++++++++ src/Psalm/Type/Union.php | 10 ++++++ tests/UnusedCodeTest.php | 1 + 4 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 src/Psalm/Internal/TypeVisitor/ContainsObjectTypeVisitor.php diff --git a/src/Psalm/Internal/Codebase/Functions.php b/src/Psalm/Internal/Codebase/Functions.php index eb73c2932be..54e1bdca1f0 100644 --- a/src/Psalm/Internal/Codebase/Functions.php +++ b/src/Psalm/Internal/Codebase/Functions.php @@ -530,12 +530,8 @@ public function isCallMapFunctionPure( if ($function_id === 'serialize' && isset($args[0]) && $type_provider) { $serialize_type = $type_provider->getType($args[0]->value); - if ($serialize_type) { - foreach ($serialize_type->getAtomicTypes() as $atomic_serialize_type) { - if ($atomic_serialize_type->isObjectType()) { - return false; - } - } + if ($serialize_type && $serialize_type->containsObjectType()) { + return false; } } diff --git a/src/Psalm/Internal/TypeVisitor/ContainsObjectTypeVisitor.php b/src/Psalm/Internal/TypeVisitor/ContainsObjectTypeVisitor.php new file mode 100644 index 00000000000..5375bcbfed7 --- /dev/null +++ b/src/Psalm/Internal/TypeVisitor/ContainsObjectTypeVisitor.php @@ -0,0 +1,36 @@ +as->hasObjectType()) + ) { + $this->contains_object_type = true; + return NodeVisitor::STOP_TRAVERSAL; + } + + return null; + } + + public function matches(): bool + { + return $this->contains_object_type; + } +} diff --git a/src/Psalm/Type/Union.php b/src/Psalm/Type/Union.php index 6ff34be3156..6a82d192e9e 100644 --- a/src/Psalm/Type/Union.php +++ b/src/Psalm/Type/Union.php @@ -9,6 +9,7 @@ use Psalm\Internal\Type\TypeCombiner; use Psalm\Internal\TypeVisitor\ContainsClassLikeVisitor; use Psalm\Internal\TypeVisitor\ContainsLiteralVisitor; +use Psalm\Internal\TypeVisitor\ContainsObjectTypeVisitor; use Psalm\Internal\TypeVisitor\FromDocblockSetter; use Psalm\Internal\TypeVisitor\TemplateTypeCollector; use Psalm\Internal\TypeVisitor\TypeChecker; @@ -1492,6 +1493,15 @@ public function containsAnyLiteral(): bool return $literal_visitor->matches(); } + public function containsObjectType(): bool + { + $object_type_visitor = new ContainsObjectTypeVisitor(); + + $object_type_visitor->traverseArray($this->types); + + return $object_type_visitor->matches(); + } + /** * @return list */ diff --git a/tests/UnusedCodeTest.php b/tests/UnusedCodeTest.php index 373ad1cf72b..e0e1baec322 100644 --- a/tests/UnusedCodeTest.php +++ b/tests/UnusedCodeTest.php @@ -754,6 +754,7 @@ public function __wakeup(): void function test(): bool { try { serialize(new Foo()); + serialize([new Foo()]); unserialize(""); } catch (\Throwable) { return false; From 6382af1c97a5561948a27d257143f3a5b6026488 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 3 Nov 2022 14:21:18 +0100 Subject: [PATCH 21/41] Add support for phpstan assertions --- .../Reflector/FunctionLikeDocblockParser.php | 56 +++++++++++-------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php index 2eb713b9458..a7900376992 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php @@ -176,7 +176,7 @@ public static function parse( } } - foreach (['psalm-self-out', 'psalm-this-out'] as $alias) { + foreach (['psalm-self-out', 'psalm-this-out', 'phpstan-self-out', 'phpstan-this-out'] as $alias) { if (isset($parsed_docblock->tags[$alias])) { foreach ($parsed_docblock->tags[$alias] as $offset => $param) { $line_parts = CommentAnalyzer::splitDocLine($param); @@ -465,36 +465,48 @@ public static function parse( } } - if (isset($parsed_docblock->tags['psalm-assert'])) { - foreach ($parsed_docblock->tags['psalm-assert'] as $assertion) { - $line_parts = self::sanitizeAssertionLineParts(CommentAnalyzer::splitDocLine($assertion)); + foreach (['psalm-assert', 'phpstan-assert'] as $assert) { + if (isset($parsed_docblock->tags[$assert])) { + foreach ($parsed_docblock->tags[$assert] as $assertion) { + $line_parts = self::sanitizeAssertionLineParts(CommentAnalyzer::splitDocLine($assertion)); - $info->assertions[] = [ - 'type' => $line_parts[0], - 'param_name' => $line_parts[1][0] === '$' ? substr($line_parts[1], 1) : $line_parts[1], - ]; + $info->assertions[] = [ + 'type' => $line_parts[0], + 'param_name' => $line_parts[1][0] === '$' ? substr($line_parts[1], 1) : $line_parts[1], + ]; + } + + break; } } - if (isset($parsed_docblock->tags['psalm-assert-if-true'])) { - foreach ($parsed_docblock->tags['psalm-assert-if-true'] as $assertion) { - $line_parts = self::sanitizeAssertionLineParts(CommentAnalyzer::splitDocLine($assertion)); + foreach (['psalm-assert-if-true', 'phpstan-assert-if-true'] as $assert) { + if (isset($parsed_docblock->tags[$assert])) { + foreach ($parsed_docblock->tags[$assert] as $assertion) { + $line_parts = self::sanitizeAssertionLineParts(CommentAnalyzer::splitDocLine($assertion)); - $info->if_true_assertions[] = [ - 'type' => $line_parts[0], - 'param_name' => $line_parts[1][0] === '$' ? substr($line_parts[1], 1) : $line_parts[1], - ]; + $info->if_true_assertions[] = [ + 'type' => $line_parts[0], + 'param_name' => $line_parts[1][0] === '$' ? substr($line_parts[1], 1) : $line_parts[1], + ]; + } + + break; } } - if (isset($parsed_docblock->tags['psalm-assert-if-false'])) { - foreach ($parsed_docblock->tags['psalm-assert-if-false'] as $assertion) { - $line_parts = self::sanitizeAssertionLineParts(CommentAnalyzer::splitDocLine($assertion)); + foreach (['psalm-assert-if-false', 'phpstan-assert-if-false'] as $assert) { + if (isset($parsed_docblock->tags[$assert])) { + foreach ($parsed_docblock->tags[$assert] as $assertion) { + $line_parts = self::sanitizeAssertionLineParts(CommentAnalyzer::splitDocLine($assertion)); - $info->if_false_assertions[] = [ - 'type' => $line_parts[0], - 'param_name' => $line_parts[1][0] === '$' ? substr($line_parts[1], 1) : $line_parts[1], - ]; + $info->if_false_assertions[] = [ + 'type' => $line_parts[0], + 'param_name' => $line_parts[1][0] === '$' ? substr($line_parts[1], 1) : $line_parts[1], + ]; + } + + break; } } From b2f095f1b8da753f42284957d885c38c8691a3e9 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 3 Nov 2022 14:39:48 +0100 Subject: [PATCH 22/41] Add tests --- tests/UnusedCodeTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/UnusedCodeTest.php b/tests/UnusedCodeTest.php index e0e1baec322..e692b2c5952 100644 --- a/tests/UnusedCodeTest.php +++ b/tests/UnusedCodeTest.php @@ -751,10 +751,12 @@ public function __wakeup(): void } } - function test(): bool { + function test(Foo|int $foo): bool { try { serialize(new Foo()); serialize([new Foo()]); + serialize([[new Foo()]]); + serialize($foo); unserialize(""); } catch (\Throwable) { return false; From 24581dfecbf71207e61c17ed0374e90b31c02d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 3 Nov 2022 15:08:27 +0100 Subject: [PATCH 23/41] Fix return type of ReflectionExtension::getVersion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- dictionaries/CallMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index cf2acbc252a..f4906d18d77 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -11444,7 +11444,7 @@ 'ReflectionExtension::getFunctions' => ['array'], 'ReflectionExtension::getINIEntries' => ['array'], 'ReflectionExtension::getName' => ['string'], -'ReflectionExtension::getVersion' => ['string'], +'ReflectionExtension::getVersion' => ['?string'], 'ReflectionExtension::info' => ['void'], 'ReflectionExtension::isPersistent' => ['bool'], 'ReflectionExtension::isTemporary' => ['bool'], From dc977964c6cf97a5eeac15c092b112b2d37bca9e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 3 Nov 2022 16:05:40 +0100 Subject: [PATCH 24/41] Refacto --- src/Psalm/Internal/Codebase/Functions.php | 2 +- .../CanContainObjectTypeVisitor.php | 51 +++++++++++++++++++ .../TypeVisitor/ContainsObjectTypeVisitor.php | 36 ------------- src/Psalm/Type/Union.php | 6 +-- 4 files changed, 55 insertions(+), 40 deletions(-) create mode 100644 src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php delete mode 100644 src/Psalm/Internal/TypeVisitor/ContainsObjectTypeVisitor.php diff --git a/src/Psalm/Internal/Codebase/Functions.php b/src/Psalm/Internal/Codebase/Functions.php index 54e1bdca1f0..daba79560a6 100644 --- a/src/Psalm/Internal/Codebase/Functions.php +++ b/src/Psalm/Internal/Codebase/Functions.php @@ -530,7 +530,7 @@ public function isCallMapFunctionPure( if ($function_id === 'serialize' && isset($args[0]) && $type_provider) { $serialize_type = $type_provider->getType($args[0]->value); - if ($serialize_type && $serialize_type->containsObjectType()) { + if ($serialize_type && $serialize_type->canContainObjectType($codebase)) { return false; } } diff --git a/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php new file mode 100644 index 00000000000..91feb240b00 --- /dev/null +++ b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php @@ -0,0 +1,51 @@ +codebase = $codebase; + } + + protected function enterNode(TypeNode $type): ?int + { + if ( + $type instanceof Union + && UnionTypeComparator::canBeContainedBy($this->codebase, $type, new Union([new TObject()])) + || + $type instanceof Atomic + && AtomicTypeComparator::isContainedBy($this->codebase, $type, new TObject()) + ) { + $this->contains_object_type = true; + return NodeVisitor::STOP_TRAVERSAL; + } + + return null; + } + + public function matches(): bool + { + return $this->contains_object_type; + } +} diff --git a/src/Psalm/Internal/TypeVisitor/ContainsObjectTypeVisitor.php b/src/Psalm/Internal/TypeVisitor/ContainsObjectTypeVisitor.php deleted file mode 100644 index 5375bcbfed7..00000000000 --- a/src/Psalm/Internal/TypeVisitor/ContainsObjectTypeVisitor.php +++ /dev/null @@ -1,36 +0,0 @@ -as->hasObjectType()) - ) { - $this->contains_object_type = true; - return NodeVisitor::STOP_TRAVERSAL; - } - - return null; - } - - public function matches(): bool - { - return $this->contains_object_type; - } -} diff --git a/src/Psalm/Type/Union.php b/src/Psalm/Type/Union.php index 6a82d192e9e..d785043dbf4 100644 --- a/src/Psalm/Type/Union.php +++ b/src/Psalm/Type/Union.php @@ -9,7 +9,7 @@ use Psalm\Internal\Type\TypeCombiner; use Psalm\Internal\TypeVisitor\ContainsClassLikeVisitor; use Psalm\Internal\TypeVisitor\ContainsLiteralVisitor; -use Psalm\Internal\TypeVisitor\ContainsObjectTypeVisitor; +use Psalm\Internal\TypeVisitor\CanContainObjectTypeVisitor; use Psalm\Internal\TypeVisitor\FromDocblockSetter; use Psalm\Internal\TypeVisitor\TemplateTypeCollector; use Psalm\Internal\TypeVisitor\TypeChecker; @@ -1493,9 +1493,9 @@ public function containsAnyLiteral(): bool return $literal_visitor->matches(); } - public function containsObjectType(): bool + public function canContainObjectType(Codebase $codebase): bool { - $object_type_visitor = new ContainsObjectTypeVisitor(); + $object_type_visitor = new CanContainObjectTypeVisitor($codebase); $object_type_visitor->traverseArray($this->types); From fbaf6afb305235a0d08de8f38293023c7c824a1a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 3 Nov 2022 18:11:01 +0100 Subject: [PATCH 25/41] Try --- .../TypeVisitor/CanContainObjectTypeVisitor.php | 10 ++++++++-- tests/UnusedCodeTest.php | 4 +++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php index 91feb240b00..ba88c86fc77 100644 --- a/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php +++ b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php @@ -32,10 +32,16 @@ protected function enterNode(TypeNode $type): ?int { if ( $type instanceof Union - && UnionTypeComparator::canBeContainedBy($this->codebase, $type, new Union([new TObject()])) + && ( + UnionTypeComparator::canBeContainedBy($this->codebase, new Union([new TObject()]), $type) + && UnionTypeComparator::canBeContainedBy($this->codebase, $type, new Union([new TObject()])) + ) || $type instanceof Atomic - && AtomicTypeComparator::isContainedBy($this->codebase, $type, new TObject()) + && ( + AtomicTypeComparator::isContainedBy($this->codebase, new TObject(), $type) + || AtomicTypeComparator::isContainedBy($this->codebase, $type, new TObject()) + ) ) { $this->contains_object_type = true; return NodeVisitor::STOP_TRAVERSAL; diff --git a/tests/UnusedCodeTest.php b/tests/UnusedCodeTest.php index e692b2c5952..0c46ed635d0 100644 --- a/tests/UnusedCodeTest.php +++ b/tests/UnusedCodeTest.php @@ -751,12 +751,14 @@ public function __wakeup(): void } } - function test(Foo|int $foo): bool { + function test(Foo|int $foo, mixed $bar, iterable $baz): bool { try { serialize(new Foo()); serialize([new Foo()]); serialize([[new Foo()]]); serialize($foo); + serialize($bar); + serialize($baz); unserialize(""); } catch (\Throwable) { return false; From c02ed9da0a2f5c0969a7fe06b9b9005d03a819c9 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 3 Nov 2022 20:24:15 +0100 Subject: [PATCH 26/41] Fix cs --- src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php | 3 +-- src/Psalm/Type/Union.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php index ba88c86fc77..460a723a474 100644 --- a/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php +++ b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php @@ -30,8 +30,7 @@ public function __construct(Codebase $codebase) protected function enterNode(TypeNode $type): ?int { - if ( - $type instanceof Union + if ($type instanceof Union && ( UnionTypeComparator::canBeContainedBy($this->codebase, new Union([new TObject()]), $type) && UnionTypeComparator::canBeContainedBy($this->codebase, $type, new Union([new TObject()])) diff --git a/src/Psalm/Type/Union.php b/src/Psalm/Type/Union.php index d785043dbf4..d7ff6557bb4 100644 --- a/src/Psalm/Type/Union.php +++ b/src/Psalm/Type/Union.php @@ -7,9 +7,9 @@ use Psalm\Codebase; use Psalm\Internal\DataFlow\DataFlowNode; use Psalm\Internal\Type\TypeCombiner; +use Psalm\Internal\TypeVisitor\CanContainObjectTypeVisitor; use Psalm\Internal\TypeVisitor\ContainsClassLikeVisitor; use Psalm\Internal\TypeVisitor\ContainsLiteralVisitor; -use Psalm\Internal\TypeVisitor\CanContainObjectTypeVisitor; use Psalm\Internal\TypeVisitor\FromDocblockSetter; use Psalm\Internal\TypeVisitor\TemplateTypeCollector; use Psalm\Internal\TypeVisitor\TypeChecker; From a1f40c9caba5004ef921c9eafed07f229db7c864 Mon Sep 17 00:00:00 2001 From: James Gilliland Date: Tue, 1 Nov 2022 09:47:16 -0500 Subject: [PATCH 27/41] Fix Spl file handling signatures Update SplFileInfo method signatures and related classes. Fixes #8642 --- dictionaries/CallMap.php | 66 ++++++++++++++--------------- dictionaries/CallMap_historical.php | 66 ++++++++++++++--------------- 2 files changed, 66 insertions(+), 66 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index cf2acbc252a..858c659ae41 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -13224,24 +13224,24 @@ 'SplFileInfo::__construct' => ['void', 'file_name'=>'string'], 'SplFileInfo::__toString' => ['string'], 'SplFileInfo::__wakeup' => ['void'], -'SplFileInfo::getATime' => ['int'], +'SplFileInfo::getATime' => ['int|false'], 'SplFileInfo::getBasename' => ['string', 'suffix='=>'string'], -'SplFileInfo::getCTime' => ['int'], +'SplFileInfo::getCTime' => ['int|false'], 'SplFileInfo::getExtension' => ['string'], 'SplFileInfo::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'], 'SplFileInfo::getFilename' => ['string'], -'SplFileInfo::getGroup' => ['int'], -'SplFileInfo::getInode' => ['int'], -'SplFileInfo::getLinkTarget' => ['string'], -'SplFileInfo::getMTime' => ['int'], -'SplFileInfo::getOwner' => ['int'], +'SplFileInfo::getGroup' => ['int|false'], +'SplFileInfo::getInode' => ['int|false'], +'SplFileInfo::getLinkTarget' => ['string|false'], +'SplFileInfo::getMTime' => ['int|false'], +'SplFileInfo::getOwner' => ['int|false'], 'SplFileInfo::getPath' => ['string'], -'SplFileInfo::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], +'SplFileInfo::getPathInfo' => ['SplFileInfo|null', 'class_name='=>'string'], 'SplFileInfo::getPathname' => ['string'], -'SplFileInfo::getPerms' => ['int'], +'SplFileInfo::getPerms' => ['int|false'], 'SplFileInfo::getRealPath' => ['string|false'], -'SplFileInfo::getSize' => ['int'], -'SplFileInfo::getType' => ['string'], +'SplFileInfo::getSize' => ['int|false'], +'SplFileInfo::getType' => ['string|false'], 'SplFileInfo::isDir' => ['bool'], 'SplFileInfo::isExecutable' => ['bool'], 'SplFileInfo::isFile' => ['bool'], @@ -13269,29 +13269,29 @@ 'SplFileObject::ftell' => ['int|false'], 'SplFileObject::ftruncate' => ['bool', 'size'=>'int'], 'SplFileObject::fwrite' => ['int', 'string'=>'string', 'length='=>'int'], -'SplFileObject::getATime' => ['int'], +'SplFileObject::getATime' => ['int|false'], 'SplFileObject::getBasename' => ['string', 'suffix='=>'string'], 'SplFileObject::getChildren' => ['null'], 'SplFileObject::getCsvControl' => ['array'], -'SplFileObject::getCTime' => ['int'], +'SplFileObject::getCTime' => ['int|false'], 'SplFileObject::getCurrentLine' => ['string|false'], 'SplFileObject::getExtension' => ['string'], 'SplFileObject::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'], 'SplFileObject::getFilename' => ['string'], 'SplFileObject::getFlags' => ['int'], -'SplFileObject::getGroup' => ['int'], -'SplFileObject::getInode' => ['int'], -'SplFileObject::getLinkTarget' => ['string'], +'SplFileObject::getGroup' => ['int|false'], +'SplFileObject::getInode' => ['int|false'], +'SplFileObject::getLinkTarget' => ['string|false'], 'SplFileObject::getMaxLineLen' => ['int'], -'SplFileObject::getMTime' => ['int'], -'SplFileObject::getOwner' => ['int'], +'SplFileObject::getMTime' => ['int|false'], +'SplFileObject::getOwner' => ['int|false'], 'SplFileObject::getPath' => ['string'], -'SplFileObject::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], +'SplFileObject::getPathInfo' => ['SplFileInfo|null', 'class_name='=>'string'], 'SplFileObject::getPathname' => ['string'], -'SplFileObject::getPerms' => ['int'], +'SplFileObject::getPerms' => ['int|false'], 'SplFileObject::getRealPath' => ['false|string'], -'SplFileObject::getSize' => ['int'], -'SplFileObject::getType' => ['string'], +'SplFileObject::getSize' => ['int|false'], +'SplFileObject::getType' => ['string|false'], 'SplFileObject::hasChildren' => ['false'], 'SplFileObject::isDir' => ['bool'], 'SplFileObject::isExecutable' => ['bool'], @@ -13462,29 +13462,29 @@ 'SplTempFileObject::ftell' => ['int'], 'SplTempFileObject::ftruncate' => ['bool', 'size'=>'int'], 'SplTempFileObject::fwrite' => ['int', 'string'=>'string', 'length='=>'int'], -'SplTempFileObject::getATime' => ['int'], +'SplTempFileObject::getATime' => ['int|false'], 'SplTempFileObject::getBasename' => ['string', 'suffix='=>'string'], 'SplTempFileObject::getChildren' => ['null'], 'SplTempFileObject::getCsvControl' => ['array'], -'SplTempFileObject::getCTime' => ['int'], +'SplTempFileObject::getCTime' => ['int|false'], 'SplTempFileObject::getCurrentLine' => ['string'], 'SplTempFileObject::getExtension' => ['string'], 'SplTempFileObject::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'], 'SplTempFileObject::getFilename' => ['string'], 'SplTempFileObject::getFlags' => ['int'], -'SplTempFileObject::getGroup' => ['int'], -'SplTempFileObject::getInode' => ['int'], -'SplTempFileObject::getLinkTarget' => ['string'], +'SplTempFileObject::getGroup' => ['int|false'], +'SplTempFileObject::getInode' => ['int|false'], +'SplTempFileObject::getLinkTarget' => ['string|false'], 'SplTempFileObject::getMaxLineLen' => ['int'], -'SplTempFileObject::getMTime' => ['int'], -'SplTempFileObject::getOwner' => ['int'], +'SplTempFileObject::getMTime' => ['int|false'], +'SplTempFileObject::getOwner' => ['int|false'], 'SplTempFileObject::getPath' => ['string'], 'SplTempFileObject::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], 'SplTempFileObject::getPathname' => ['string'], -'SplTempFileObject::getPerms' => ['int'], -'SplTempFileObject::getRealPath' => ['string'], -'SplTempFileObject::getSize' => ['int'], -'SplTempFileObject::getType' => ['string'], +'SplTempFileObject::getPerms' => ['int|false'], +'SplTempFileObject::getRealPath' => ['string|false'], +'SplTempFileObject::getSize' => ['int|false'], +'SplTempFileObject::getType' => ['string|false'], 'SplTempFileObject::hasChildren' => ['bool'], 'SplTempFileObject::isDir' => ['bool'], 'SplTempFileObject::isExecutable' => ['bool'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 290bf708754..95902245567 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -7757,24 +7757,24 @@ 'SplFileInfo::__construct' => ['void', 'file_name'=>'string'], 'SplFileInfo::__toString' => ['string'], 'SplFileInfo::__wakeup' => ['void'], - 'SplFileInfo::getATime' => ['int'], + 'SplFileInfo::getATime' => ['int|false'], 'SplFileInfo::getBasename' => ['string', 'suffix='=>'string'], - 'SplFileInfo::getCTime' => ['int'], + 'SplFileInfo::getCTime' => ['int|false'], 'SplFileInfo::getExtension' => ['string'], 'SplFileInfo::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'], 'SplFileInfo::getFilename' => ['string'], - 'SplFileInfo::getGroup' => ['int'], - 'SplFileInfo::getInode' => ['int'], - 'SplFileInfo::getLinkTarget' => ['string'], - 'SplFileInfo::getMTime' => ['int'], - 'SplFileInfo::getOwner' => ['int'], + 'SplFileInfo::getGroup' => ['int|false'], + 'SplFileInfo::getInode' => ['int|false'], + 'SplFileInfo::getLinkTarget' => ['string|false'], + 'SplFileInfo::getMTime' => ['int|false'], + 'SplFileInfo::getOwner' => ['int|false'], 'SplFileInfo::getPath' => ['string'], - 'SplFileInfo::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], + 'SplFileInfo::getPathInfo' => ['SplFileInfo|null', 'class_name='=>'string'], 'SplFileInfo::getPathname' => ['string'], - 'SplFileInfo::getPerms' => ['int'], + 'SplFileInfo::getPerms' => ['int|false'], 'SplFileInfo::getRealPath' => ['string|false'], - 'SplFileInfo::getSize' => ['int'], - 'SplFileInfo::getType' => ['string'], + 'SplFileInfo::getSize' => ['int|false'], + 'SplFileInfo::getType' => ['string|false'], 'SplFileInfo::isDir' => ['bool'], 'SplFileInfo::isExecutable' => ['bool'], 'SplFileInfo::isFile' => ['bool'], @@ -7803,9 +7803,9 @@ 'SplFileObject::ftell' => ['int|false'], 'SplFileObject::ftruncate' => ['bool', 'size'=>'int'], 'SplFileObject::fwrite' => ['int', 'string'=>'string', 'length='=>'int'], - 'SplFileObject::getATime' => ['int'], + 'SplFileObject::getATime' => ['int|false'], 'SplFileObject::getBasename' => ['string', 'suffix='=>'string'], - 'SplFileObject::getCTime' => ['int'], + 'SplFileObject::getCTime' => ['int|false'], 'SplFileObject::getChildren' => ['null'], 'SplFileObject::getCsvControl' => ['array'], 'SplFileObject::getCurrentLine' => ['string|false'], @@ -7813,19 +7813,19 @@ 'SplFileObject::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'], 'SplFileObject::getFilename' => ['string'], 'SplFileObject::getFlags' => ['int'], - 'SplFileObject::getGroup' => ['int'], - 'SplFileObject::getInode' => ['int'], - 'SplFileObject::getLinkTarget' => ['string'], - 'SplFileObject::getMTime' => ['int'], + 'SplFileObject::getGroup' => ['int|false'], + 'SplFileObject::getInode' => ['int|false'], + 'SplFileObject::getLinkTarget' => ['string|false'], 'SplFileObject::getMaxLineLen' => ['int'], - 'SplFileObject::getOwner' => ['int'], + 'SplFileObject::getMTime' => ['int|false'], + 'SplFileObject::getOwner' => ['int|false'], 'SplFileObject::getPath' => ['string'], - 'SplFileObject::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], + 'SplFileObject::getPathInfo' => ['SplFileInfo|null', 'class_name='=>'string'], 'SplFileObject::getPathname' => ['string'], - 'SplFileObject::getPerms' => ['int'], + 'SplFileObject::getPerms' => ['int|false'], 'SplFileObject::getRealPath' => ['false|string'], - 'SplFileObject::getSize' => ['int'], - 'SplFileObject::getType' => ['string'], + 'SplFileObject::getSize' => ['int|false'], + 'SplFileObject::getType' => ['string|false'], 'SplFileObject::hasChildren' => ['false'], 'SplFileObject::isDir' => ['bool'], 'SplFileObject::isExecutable' => ['bool'], @@ -7995,9 +7995,9 @@ 'SplTempFileObject::ftell' => ['int'], 'SplTempFileObject::ftruncate' => ['bool', 'size'=>'int'], 'SplTempFileObject::fwrite' => ['int', 'string'=>'string', 'length='=>'int'], - 'SplTempFileObject::getATime' => ['int'], + 'SplTempFileObject::getATime' => ['int|false'], 'SplTempFileObject::getBasename' => ['string', 'suffix='=>'string'], - 'SplTempFileObject::getCTime' => ['int'], + 'SplTempFileObject::getCTime' => ['int|false'], 'SplTempFileObject::getChildren' => ['null'], 'SplTempFileObject::getCsvControl' => ['array'], 'SplTempFileObject::getCurrentLine' => ['string'], @@ -8005,19 +8005,19 @@ 'SplTempFileObject::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'], 'SplTempFileObject::getFilename' => ['string'], 'SplTempFileObject::getFlags' => ['int'], - 'SplTempFileObject::getGroup' => ['int'], - 'SplTempFileObject::getInode' => ['int'], - 'SplTempFileObject::getLinkTarget' => ['string'], - 'SplTempFileObject::getMTime' => ['int'], + 'SplTempFileObject::getGroup' => ['int|false'], + 'SplTempFileObject::getInode' => ['int|false'], + 'SplTempFileObject::getLinkTarget' => ['string|false'], 'SplTempFileObject::getMaxLineLen' => ['int'], - 'SplTempFileObject::getOwner' => ['int'], + 'SplTempFileObject::getMTime' => ['int|false'], + 'SplTempFileObject::getOwner' => ['int|false'], 'SplTempFileObject::getPath' => ['string'], 'SplTempFileObject::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], 'SplTempFileObject::getPathname' => ['string'], - 'SplTempFileObject::getPerms' => ['int'], - 'SplTempFileObject::getRealPath' => ['string'], - 'SplTempFileObject::getSize' => ['int'], - 'SplTempFileObject::getType' => ['string'], + 'SplTempFileObject::getPerms' => ['int|false'], + 'SplTempFileObject::getRealPath' => ['string|false'], + 'SplTempFileObject::getSize' => ['int|false'], + 'SplTempFileObject::getType' => ['string|false'], 'SplTempFileObject::hasChildren' => ['bool'], 'SplTempFileObject::isDir' => ['bool'], 'SplTempFileObject::isExecutable' => ['bool'], From 81423dc12b5a7af4c058b0ae7d662203647b77a5 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Sat, 5 Nov 2022 15:19:21 -0400 Subject: [PATCH 28/41] Remove `argc` and `argv` elements from `$_ENV` Fixes vimeo/psalm#8662 --- .../Expression/Fetch/VariableFetchAnalyzer.php | 14 ++++++++++---- tests/SuperGlobalsTest.php | 9 +++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index 2d7f5bca7cc..26ecddde3be 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -650,11 +650,9 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ $bool_string_helper = new Union([new TBool(), new TString()]); $bool_string_helper->possibly_undefined = true; - $detailed_type = new TKeyedArray([ + $detailed_type_members = [ // https://www.php.net/manual/en/reserved.variables.server.php 'PHP_SELF' => $non_empty_string_helper, - 'argv' => $argv_helper, - 'argc' => $argc_helper, 'GATEWAY_INTERFACE' => $non_empty_string_helper, 'SERVER_ADDR' => $non_empty_string_helper, 'SERVER_NAME' => $non_empty_string_helper, @@ -727,7 +725,15 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ // phpunit 'APP_DEBUG' => $bool_string_helper, 'APP_ENV' => $string_helper, - ]); + ]; + + if ($var_id === '$_SERVER') { + // those elements are not usually present in $_ENV + $detailed_type_members['argc'] = $argc_helper; + $detailed_type_members['argv'] = $argv_helper; + } + + $detailed_type = new TKeyedArray($detailed_type_members); // generic case for all other elements $detailed_type->previous_key_type = Type::getNonEmptyString(); diff --git a/tests/SuperGlobalsTest.php b/tests/SuperGlobalsTest.php index d13919f2345..ad052fd56b3 100644 --- a/tests/SuperGlobalsTest.php +++ b/tests/SuperGlobalsTest.php @@ -22,5 +22,14 @@ function returnsList(): array { ', 'assertions' => [] ]; + + yield 'ENV has scalar entries only' => [ + ' */ + function f(): array { + return $_ENV; + } + ' + ]; } } From b87ebd697f63836bfe0412ab21cb7204f010b8ee Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 6 Nov 2022 00:58:50 -0500 Subject: [PATCH 29/41] Fix assert testing callmap return types --- dictionaries/CallMap.php | 8 +-- dictionaries/CallMap_historical.php | 8 +-- .../Codebase/InternalCallMapHandlerTest.php | 60 ++----------------- 3 files changed, 14 insertions(+), 62 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 858c659ae41..c060b68508c 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -9281,7 +9281,7 @@ 'ocigetbufferinglob' => ['bool'], 'ocisetbufferinglob' => ['bool', 'lob'=>'bool'], 'octdec' => ['int|float', 'octal_string'=>'string'], -'odbc_autocommit' => ['mixed', 'odbc'=>'resource', 'enable='=>'bool'], +'odbc_autocommit' => ['int|bool', 'odbc'=>'resource', 'enable='=>'bool'], 'odbc_binmode' => ['bool', 'statement'=>'resource', 'mode'=>'int'], 'odbc_close' => ['void', 'odbc'=>'resource'], 'odbc_close_all' => ['void'], @@ -9298,7 +9298,7 @@ 'odbc_execute' => ['bool', 'statement'=>'resource', 'params='=>'array'], 'odbc_fetch_array' => ['array|false', 'statement'=>'resource', 'row='=>'int'], 'odbc_fetch_into' => ['int', 'statement'=>'resource', '&w_array'=>'array', 'row='=>'int'], -'odbc_fetch_object' => ['object|false', 'statement'=>'resource', 'row='=>'int'], +'odbc_fetch_object' => ['stdClass|false', 'statement'=>'resource', 'row='=>'int'], 'odbc_fetch_row' => ['bool', 'statement'=>'resource', 'row='=>'int'], 'odbc_field_len' => ['int|false', 'statement'=>'resource', 'field'=>'int'], 'odbc_field_name' => ['string|false', 'statement'=>'resource', 'field'=>'int'], @@ -13683,7 +13683,7 @@ 'ssh2_exec' => ['resource|false', 'session'=>'resource', 'command'=>'string', 'pty='=>'string', 'env='=>'array', 'width='=>'int', 'height='=>'int', 'width_height_type='=>'int'], 'ssh2_fetch_stream' => ['resource|false', 'channel'=>'resource', 'streamid'=>'int'], 'ssh2_fingerprint' => ['string|false', 'session'=>'resource', 'flags='=>'int'], -'ssh2_forward_accept' => ['resource|false', 'session'=>'resource'], +'ssh2_forward_accept' => ['resource|false', 'listener'=>'resource'], 'ssh2_forward_listen' => ['resource|false', 'session'=>'resource', 'port'=>'int', 'host='=>'string', 'max_connections='=>'string'], 'ssh2_methods_negotiated' => ['array|false', 'session'=>'resource'], 'ssh2_poll' => ['int', '&polldes'=>'array', 'timeout='=>'int'], @@ -13704,7 +13704,7 @@ 'ssh2_sftp_stat' => ['array{0: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int, 7: int, 8: int, 9: int, 10: int, 11: int, 12: int, dev: int, ino: int, mode: int, nlink: int, uid: int, gid: int, rdev: int, size: int, atime: int, mtime: int, ctime: int, blksize: int, blocks: int}|false', 'sftp'=>'resource', 'path'=>'string'], 'ssh2_sftp_symlink' => ['bool', 'sftp'=>'resource', 'target'=>'string', 'link'=>'string'], 'ssh2_sftp_unlink' => ['bool', 'sftp'=>'resource', 'filename'=>'string'], -'ssh2_shell' => ['resource|false', 'session'=>'resource', 'term_type='=>'string', 'env='=>'array', 'width='=>'int', 'height='=>'int', 'width_height_type='=>'int'], +'ssh2_shell' => ['resource|false', 'session'=>'resource', 'termtype='=>'string', 'env='=>'array', 'width='=>'int', 'height='=>'int', 'width_height_type='=>'int'], 'ssh2_tunnel' => ['resource|false', 'session'=>'resource', 'host'=>'string', 'port'=>'int'], 'stat' => ['array{0: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int, 7: int, 8: int, 9: int, 10: int, 11: int, 12: int, dev: int, ino: int, mode: int, nlink: int, uid: int, gid: int, rdev: int, size: int, atime: int, mtime: int, ctime: int, blksize: int, blocks: int}|false', 'filename'=>'string'], 'stats_absolute_deviation' => ['float', 'a'=>'array'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 95902245567..b127458551a 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -14056,7 +14056,7 @@ 'ocigetbufferinglob' => ['bool'], 'ocisetbufferinglob' => ['bool', 'lob'=>'bool'], 'octdec' => ['int|float', 'octal_string'=>'string'], - 'odbc_autocommit' => ['mixed', 'odbc'=>'resource', 'enable='=>'bool'], + 'odbc_autocommit' => ['int|bool', 'odbc'=>'resource', 'enable='=>'bool'], 'odbc_binmode' => ['bool', 'statement'=>'resource', 'mode'=>'int'], 'odbc_close' => ['void', 'odbc'=>'resource'], 'odbc_close_all' => ['void'], @@ -14073,7 +14073,7 @@ 'odbc_execute' => ['bool', 'statement'=>'resource', 'params='=>'array'], 'odbc_fetch_array' => ['array|false', 'statement'=>'resource', 'row='=>'int'], 'odbc_fetch_into' => ['int', 'statement'=>'resource', '&w_array'=>'array', 'row='=>'int'], - 'odbc_fetch_object' => ['object|false', 'statement'=>'resource', 'row='=>'int'], + 'odbc_fetch_object' => ['stdClass|false', 'statement'=>'resource', 'row='=>'int'], 'odbc_fetch_row' => ['bool', 'statement'=>'resource', 'row='=>'int'], 'odbc_field_len' => ['int|false', 'statement'=>'resource', 'field'=>'int'], 'odbc_field_name' => ['string|false', 'statement'=>'resource', 'field'=>'int'], @@ -15107,7 +15107,7 @@ 'ssh2_exec' => ['resource|false', 'session'=>'resource', 'command'=>'string', 'pty='=>'string', 'env='=>'array', 'width='=>'int', 'height='=>'int', 'width_height_type='=>'int'], 'ssh2_fetch_stream' => ['resource|false', 'channel'=>'resource', 'streamid'=>'int'], 'ssh2_fingerprint' => ['string|false', 'session'=>'resource', 'flags='=>'int'], - 'ssh2_forward_accept' => ['resource|false', 'session'=>'resource'], + 'ssh2_forward_accept' => ['resource|false', 'listener'=>'resource'], 'ssh2_forward_listen' => ['resource|false', 'session'=>'resource', 'port'=>'int', 'host='=>'string', 'max_connections='=>'string'], 'ssh2_methods_negotiated' => ['array|false', 'session'=>'resource'], 'ssh2_poll' => ['int', '&polldes'=>'array', 'timeout='=>'int'], @@ -15128,7 +15128,7 @@ 'ssh2_sftp_stat' => ['array{0: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int, 7: int, 8: int, 9: int, 10: int, 11: int, 12: int, dev: int, ino: int, mode: int, nlink: int, uid: int, gid: int, rdev: int, size: int, atime: int, mtime: int, ctime: int, blksize: int, blocks: int}|false', 'sftp'=>'resource', 'path'=>'string'], 'ssh2_sftp_symlink' => ['bool', 'sftp'=>'resource', 'target'=>'string', 'link'=>'string'], 'ssh2_sftp_unlink' => ['bool', 'sftp'=>'resource', 'filename'=>'string'], - 'ssh2_shell' => ['resource|false', 'session'=>'resource', 'term_type='=>'string', 'env='=>'array', 'width='=>'int', 'height='=>'int', 'width_height_type='=>'int'], + 'ssh2_shell' => ['resource|false', 'session'=>'resource', 'termtype='=>'string', 'env='=>'array', 'width='=>'int', 'height='=>'int', 'width_height_type='=>'int'], 'ssh2_tunnel' => ['resource|false', 'session'=>'resource', 'host'=>'string', 'port'=>'int'], 'stat' => ['array{0: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int, 7: int, 8: int, 9: int, 10: int, 11: int, 12: int, dev: int, ino: int, mode: int, nlink: int, uid: int, gid: int, rdev: int, size: int, atime: int, mtime: int, ctime: int, blksize: int, blocks: int}|false', 'filename'=>'string'], 'stats_absolute_deviation' => ['float', 'a'=>'array'], diff --git a/tests/Internal/Codebase/InternalCallMapHandlerTest.php b/tests/Internal/Codebase/InternalCallMapHandlerTest.php index 16eb66e9a3a..18cd54349e7 100644 --- a/tests/Internal/Codebase/InternalCallMapHandlerTest.php +++ b/tests/Internal/Codebase/InternalCallMapHandlerTest.php @@ -282,6 +282,7 @@ class InternalCallMapHandlerTest extends TestCase 'sqlsrv_prepare', 'sqlsrv_query', 'sqlsrv_server_info', + 'ssh2_forward_accept', 'stomp_abort', 'stomp_ack', 'stomp_begin', @@ -336,7 +337,6 @@ class InternalCallMapHandlerTest extends TestCase */ private static $ignoredReturnTypeOnlyFunctions = [ 'bcsqrt', - 'bzopen', 'cal_from_jd', 'collator_get_strength', 'curl_multi_init', @@ -354,25 +354,19 @@ class InternalCallMapHandlerTest extends TestCase 'date_timestamp_set', 'date_timezone_set', 'datefmt_set_lenient', - 'dba_open', - 'dba_popen', 'deflate_init', 'enchant_broker_init', 'fgetcsv', 'filter_input_array', - 'fopen', 'fpassthru', - 'fsockopen', 'ftp_get_option', 'get_declared_traits', 'gmp_export', 'gmp_hamdist', 'gmp_import', 'gzeof', - 'gzopen', 'gzpassthru', 'iconv_get_encoding', - 'igbinary_serialize', 'imagecolorclosest', 'imagecolorclosestalpha', 'imagecolorclosesthwb', @@ -402,31 +396,11 @@ class InternalCallMapHandlerTest extends TestCase 'ldap_get_attributes', 'mb_encoding_aliases', 'metaphone', - 'mongodb\\bson\\fromjson', - 'mongodb\\bson\\fromphp', - 'mongodb\\bson\\tojson', 'msg_get_queue', 'mysqli_stmt_get_warnings', 'mysqli_stmt_insert_id', 'numfmt_create', 'ob_list_handlers', - 'odbc_autocommit', - 'odbc_columnprivileges', - 'odbc_columns', - 'odbc_connect', - 'odbc_do', - 'odbc_exec', - 'odbc_fetch_object', - 'odbc_foreignkeys', - 'odbc_gettypeinfo', - 'odbc_pconnect', - 'odbc_prepare', - 'odbc_primarykeys', - 'odbc_specialcolumns', - 'odbc_statistics', - 'odbc_tableprivileges', - 'odbc_tables', - 'opendir', 'openssl_random_pseudo_bytes', 'openssl_spki_export', 'openssl_spki_export_challenge', @@ -434,58 +408,36 @@ class InternalCallMapHandlerTest extends TestCase 'parse_url', 'passthru', 'pcntl_exec', - 'pcntl_signal_get_handler', 'pcntl_strerror', - 'pfsockopen', 'pg_port', - 'pg_socket', - 'popen', - 'proc_open', 'pspell_config_create', 'pspell_new', 'pspell_new_config', 'pspell_new_personal', 'register_shutdown_function', 'rewinddir', - 'set_error_handler', - 'set_exception_handler', 'shm_attach', 'shmop_open', 'simplexml_import_dom', 'sleep', 'snmp_set_oid_numeric_print', - 'socket_export_stream', 'socket_import_stream', 'sodium_crypto_aead_chacha20poly1305_encrypt', 'sodium_crypto_aead_chacha20poly1305_ietf_encrypt', 'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt', 'spl_autoload_functions', 'stream_bucket_new', - 'stream_context_create', - 'stream_context_get_default', - 'stream_context_set_default', - 'stream_filter_append', - 'stream_filter_prepend', 'stream_set_chunk_size', - 'stream_socket_accept', - 'stream_socket_client', - 'stream_socket_server', 'substr', 'substr_compare', 'timezone_abbreviations_list', 'timezone_offset_get', - 'tmpfile', 'user_error', 'xml_get_current_byte_index', 'xml_get_current_column_number', 'xml_get_current_line_number', 'xml_get_error_code', 'xml_parser_get_option', - 'yaml_parse', - 'yaml_parse_file', - 'yaml_parse_url', - 'zip_open', - 'zip_read', ]; /** @@ -767,12 +719,12 @@ public function assertEntryReturnType(ReflectionFunction $function, string $entr } else { $expectedType = $function->getReturnType(); } - if ($expectedType === null) { - $this->assertSame('', $entryReturnType, 'CallMap entry has incorrect return type'); - return; - } - $this->assertTypeValidity($expectedType, $entryReturnType, true, 'CallMap entry has incorrect return type'); + if ($expectedType !== null) { + $this->assertTypeValidity($expectedType, $entryReturnType, true, 'CallMap entry has incorrect return type'); + } else { + $this->assertNotEmpty($entryReturnType); + } } /** From 74111a3e934584ffeb2ab047d5852c069bf50a5a Mon Sep 17 00:00:00 2001 From: orklah Date: Sun, 6 Nov 2022 21:22:57 +0100 Subject: [PATCH 30/41] adding openssl_x509_verify --- dictionaries/CallMap.php | 1 + dictionaries/CallMap_74_delta.php | 1 + dictionaries/CallMap_80_delta.php | 4 ++++ src/Psalm/Internal/Type/AssertionReconciler.php | 2 ++ 4 files changed, 8 insertions(+) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index c060b68508c..a5d8fc1e8ef 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -9415,6 +9415,7 @@ 'openssl_x509_free' => ['void', 'certificate'=>'OpenSSLCertificate'], 'openssl_x509_parse' => ['array|false', 'certificate'=>'OpenSSLCertificate|string', 'short_names='=>'bool'], 'openssl_x509_read' => ['OpenSSLCertificate|false', 'certificate'=>'OpenSSLCertificate|string'], +'openssl_x509_verify' => ['int', 'certificate'=>'string|OpenSSLCertificate', 'public_key'=>'string|OpenSSLCertificate|OpenSSLAsymmetricKey|array'], 'ord' => ['int', 'character'=>'string'], 'OuterIterator::current' => ['mixed'], 'OuterIterator::getInnerIterator' => ['Iterator'], diff --git a/dictionaries/CallMap_74_delta.php b/dictionaries/CallMap_74_delta.php index af5e0d976bf..6226077179f 100644 --- a/dictionaries/CallMap_74_delta.php +++ b/dictionaries/CallMap_74_delta.php @@ -18,6 +18,7 @@ 'added' => [ 'ReflectionProperty::getType' => ['?ReflectionType'], 'mb_str_split' => ['list|false', 'string'=>'string', 'length='=>'positive-int', 'encoding='=>'string'], + 'openssl_x509_verify' => ['int', 'certificate'=>'string|resource', 'public_key'=>'string|array|resource'], ], 'changed' => [ 'array_merge' => [ diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index a16d4967141..cd644320157 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -1193,6 +1193,10 @@ 'old' => ['resource|false', 'certificate'=>'string|resource'], 'new' => ['OpenSSLCertificate|false', 'certificate'=>'OpenSSLCertificate|string'], ], + 'openssl_x509_verify' => [ + 'old' => ['int', 'certificate'=>'string|resource', 'public_key'=>'string|array|resource'], + 'new' => ['int', 'certificate'=>'string|OpenSSLCertificate', 'public_key'=>'string|OpenSSLCertificate|OpenSSLAsymmetricKey|array'], + ], 'parse_str' => [ 'old' => ['void', 'string'=>'string', '&w_result='=>'array'], 'new' => ['void', 'string'=>'string', '&w_result'=>'array'], diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index e72954cbaf0..1fa4c5b5965 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -81,6 +81,8 @@ public static function reconcile( ?int &$failed_reconciliation = Reconciler::RECONCILIATION_OK, bool $negated = false ): Union { + var_dump($assertion); + var_dump($existing_var_type->getId()); $codebase = $statements_analyzer->getCodebase(); $is_strict_equality = false; From 398cf99c6c8d395443a0136f8bbce83fcdc2ba6f Mon Sep 17 00:00:00 2001 From: orklah Date: Sun, 6 Nov 2022 21:24:03 +0100 Subject: [PATCH 31/41] revert wrong commit --- src/Psalm/Internal/Type/AssertionReconciler.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 1fa4c5b5965..e72954cbaf0 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -81,8 +81,6 @@ public static function reconcile( ?int &$failed_reconciliation = Reconciler::RECONCILIATION_OK, bool $negated = false ): Union { - var_dump($assertion); - var_dump($existing_var_type->getId()); $codebase = $statements_analyzer->getCodebase(); $is_strict_equality = false; From 8e70aa3a7739cefb2d9aef3f5a3ba1d1bcb37a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 7 Nov 2022 09:12:36 +0100 Subject: [PATCH 32/41] Fix ReflectionExtension::getVersion signature in CallMap_historical.php as well MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- 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 290bf708754..017ffce9e63 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -6071,7 +6071,7 @@ 'ReflectionExtension::getFunctions' => ['array'], 'ReflectionExtension::getINIEntries' => ['array'], 'ReflectionExtension::getName' => ['string'], - 'ReflectionExtension::getVersion' => ['string'], + 'ReflectionExtension::getVersion' => ['?string'], 'ReflectionExtension::info' => ['void'], 'ReflectionExtension::isPersistent' => ['bool'], 'ReflectionExtension::isTemporary' => ['bool'], From 6916d414e2f4589e5b9f755cda28c161d9d4a735 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 7 Nov 2022 10:22:33 +0100 Subject: [PATCH 33/41] Add support for phpstan-param-out --- src/Psalm/Internal/Scanner/DocblockParser.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Psalm/Internal/Scanner/DocblockParser.php b/src/Psalm/Internal/Scanner/DocblockParser.php index 22ae1ed258f..329fed06151 100644 --- a/src/Psalm/Internal/Scanner/DocblockParser.php +++ b/src/Psalm/Internal/Scanner/DocblockParser.php @@ -258,9 +258,11 @@ private static function resolveTags(ParsedDocblock $docblock): void if (isset($docblock->tags['param-out']) || isset($docblock->tags['psalm-param-out']) + || isset($docblock->tags['phpstan-param-out']) ) { $docblock->combined_tags['param-out'] = ($docblock->tags['param-out'] ?? []) + + ($docblock->tags['phpstan-param-out'] ?? []) + ($docblock->tags['psalm-param-out'] ?? []); } } From 940e00e57a856b66798b0bd307edf3838d4fc56f Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 8 Nov 2022 10:26:28 +0100 Subject: [PATCH 34/41] Fix --- src/Psalm/Type/UnionTrait.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Psalm/Type/UnionTrait.php b/src/Psalm/Type/UnionTrait.php index 2df7db1515e..6ca78a19fb7 100644 --- a/src/Psalm/Type/UnionTrait.php +++ b/src/Psalm/Type/UnionTrait.php @@ -5,6 +5,7 @@ use InvalidArgumentException; use Psalm\CodeLocation; use Psalm\Codebase; +use Psalm\Internal\TypeVisitor\CanContainObjectTypeVisitor; use Psalm\Internal\TypeVisitor\ClasslikeReplacer; use Psalm\Internal\TypeVisitor\ContainsClassLikeVisitor; use Psalm\Internal\TypeVisitor\ContainsLiteralVisitor; @@ -498,6 +499,18 @@ public function hasObjectType(): bool return false; } + /** + * @psalm-mutation-free + */ + public function canContainObjectType(Codebase $codebase): bool + { + $object_type_visitor = new CanContainObjectTypeVisitor($codebase); + + $object_type_visitor->traverseArray($this->types); + + return $object_type_visitor->matches(); + } + /** * @psalm-mutation-free */ From 383cec68637742e56863c590bd3997b53d5dece6 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 8 Nov 2022 10:28:51 +0100 Subject: [PATCH 35/41] Add missing docs --- docs/annotating_code/type_syntax/other_types.md | 6 ++++++ .../type_syntax/top_bottom_types.md | 15 +++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 docs/annotating_code/type_syntax/other_types.md create mode 100644 docs/annotating_code/type_syntax/top_bottom_types.md diff --git a/docs/annotating_code/type_syntax/other_types.md b/docs/annotating_code/type_syntax/other_types.md new file mode 100644 index 00000000000..d8744e7cb0d --- /dev/null +++ b/docs/annotating_code/type_syntax/other_types.md @@ -0,0 +1,6 @@ +# Other types + +- `iterable` - represents the [iterable pseudo-type](https://php.net/manual/en/language.types.iterable.php). Like arrays, iterables can have type parameters e.g. `iterable`. +- `void` - can be used in a return type when a function does not return a value. +- `resource` represents a [PHP resource](https://www.php.net/manual/en/language.types.resource.php). +- `closed-resource` represents a [PHP resource](https://www.php.net/manual/en/language.types.resource.php) that was closed (using `fclose` or another closing function). diff --git a/docs/annotating_code/type_syntax/top_bottom_types.md b/docs/annotating_code/type_syntax/top_bottom_types.md new file mode 100644 index 00000000000..324c021a72a --- /dev/null +++ b/docs/annotating_code/type_syntax/top_bottom_types.md @@ -0,0 +1,15 @@ + +# Top types, bottom types + +## `mixed` + +This is the _top type_ in PHP's type system, and represents a lack of type information. Psalm warns about `mixed` types when the `reportMixedIssues` flag is turned on, or when you're on level 1. + +## `never` +It can be aliased to `no-return` or `never-return` in docblocks. Note: it replaced the old `empty` type that used to exist in Psalm + +This is the _bottom type_ in PHP's type system. It's used to describe a type that has no possible value. It can happen in multiple cases: +- the actual `never` type from PHP 8.1 (can be used in docblocks for older versions). This type can be used as a return type for functions that will never return, either because they always throw exceptions or always exit() +- an union type that have been stripped for all its possible types. (For example, if a variable is `string|int` and we perform a is_bool() check in a condition, the type of the variable in the condition will be `never` as the condition will never be entered) +- it can represent a placeholder for types yet to come — a good example is the type of the empty array `[]`, which Psalm types as `array`, the content of the array is void so it can accept any content +- it can also happen in the same context as the line above for templates that have yet to be defined From fc00b6338a9f1f4bc70353e2d24699b11c279a2e Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 8 Nov 2022 10:37:48 +0100 Subject: [PATCH 36/41] Fix tests --- .../Internal/TypeVisitor/CanContainObjectTypeVisitor.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php index f2c6b51dc84..cea996b799e 100644 --- a/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php +++ b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php @@ -3,11 +3,8 @@ namespace Psalm\Internal\TypeVisitor; use Psalm\Codebase; -use Psalm\Internal\Type\Comparator\AtomicTypeComparator; -use Psalm\Internal\Type\Comparator\UnionTypeComparator; use Psalm\Type\Atomic; -use Psalm\Type\Atomic\TObject; -use Psalm\Type\NodeVisitor; +use Psalm\Type\Atomic\TMixed; use Psalm\Type\TypeNode; use Psalm\Type\TypeVisitor; use Psalm\Type\Union; @@ -32,9 +29,9 @@ public function __construct(Codebase $codebase) protected function enterNode(TypeNode $type): ?int { if (($type instanceof Union - && $type->hasObjectType() + && ($type->hasObjectType() || $type->hasIterable() || $type->hasMixed()) ) || ($type instanceof Atomic - && $type->isObjectType() + && ($type->isObjectType() || $type->isIterable($this->codebase) || $type instanceof TMixed) )) { $this->contains_object_type = true; return self::STOP_TRAVERSAL; From 65d6a469b67788f84e2d8e8d558277d2a4397710 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 8 Nov 2022 10:45:21 +0100 Subject: [PATCH 37/41] Fix tests --- src/Psalm/Internal/Clause.php | 1 + src/Psalm/Type/TypeVisitor.php | 2 -- src/Psalm/Type/UnionTrait.php | 1 + tests/ArrayFunctionCallTest.php | 18 +++++++++--------- tests/SuperGlobalsTest.php | 2 +- tests/UnusedCodeTest.php | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Psalm/Internal/Clause.php b/src/Psalm/Internal/Clause.php index 4f4f66e449c..b13fa913467 100644 --- a/src/Psalm/Internal/Clause.php +++ b/src/Psalm/Internal/Clause.php @@ -111,6 +111,7 @@ public function __construct( $possibility_strings[$i] = array_keys($v); } + /** @psalm-suppress ImpureFunctionCall */ $data = serialize($possibility_strings); $this->hash = PHP_VERSION_ID >= 8_01_00 ? hash('xxh128', $data) : hash('md4', $data); } diff --git a/src/Psalm/Type/TypeVisitor.php b/src/Psalm/Type/TypeVisitor.php index 8e9122ff455..477f505e122 100644 --- a/src/Psalm/Type/TypeVisitor.php +++ b/src/Psalm/Type/TypeVisitor.php @@ -8,8 +8,6 @@ abstract class TypeVisitor public const DONT_TRAVERSE_CHILDREN = 2; /** - * @internal Can only be called by a TypeNode - * * @return self::STOP_TRAVERSAL|self::DONT_TRAVERSE_CHILDREN|null */ abstract protected function enterNode(TypeNode $type): ?int; diff --git a/src/Psalm/Type/UnionTrait.php b/src/Psalm/Type/UnionTrait.php index 6ca78a19fb7..fe84bd74409 100644 --- a/src/Psalm/Type/UnionTrait.php +++ b/src/Psalm/Type/UnionTrait.php @@ -1271,6 +1271,7 @@ public function queueClassLikesForScanning( $phantom_classes ); + /** @psalm-suppress ImpureMethodCall */ $scanner_visitor->traverseArray($this->types); } diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index 01356c1e2ce..48eafc50029 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -1108,7 +1108,7 @@ function foo(array $arr) { }', ], 'current' => [ - ' ' 1, "two" => 3]; $b = current($a);', 'assertions' => [ @@ -1116,7 +1116,7 @@ function foo(array $arr) { ], ], 'currentEmptyArray' => [ - ' ' [ @@ -1124,7 +1124,7 @@ function foo(array $arr) { ], ], 'currentNonEmptyArray' => [ - ' ' $arr * @return int @@ -1134,7 +1134,7 @@ function foo(array $arr) { }', ], 'reset' => [ - ' ' 1, "two" => 3]; $b = reset($a);', 'assertions' => [ @@ -1142,7 +1142,7 @@ function foo(array $arr) { ], ], 'resetEmptyArray' => [ - ' ' [ @@ -1150,7 +1150,7 @@ function foo(array $arr) { ], ], 'resetNonEmptyArray' => [ - ' ' $arr * @return int @@ -1160,7 +1160,7 @@ function foo(array $arr) { }', ], 'end' => [ - ' ' 1, "two" => 3]; $b = end($a);', 'assertions' => [ @@ -1168,7 +1168,7 @@ function foo(array $arr) { ], ], 'endEmptyArray' => [ - ' ' [ @@ -1176,7 +1176,7 @@ function foo(array $arr) { ], ], 'endNonEmptyArray' => [ - ' ' $arr * @return int diff --git a/tests/SuperGlobalsTest.php b/tests/SuperGlobalsTest.php index fb0faf61223..f3d70f36caf 100644 --- a/tests/SuperGlobalsTest.php +++ b/tests/SuperGlobalsTest.php @@ -24,7 +24,7 @@ function returnsList(): array { ]; yield 'ENV has scalar entries only' => [ - ' ' */ function f(): array { return $_ENV; diff --git a/tests/UnusedCodeTest.php b/tests/UnusedCodeTest.php index 35bb2bf3689..43e078100e7 100644 --- a/tests/UnusedCodeTest.php +++ b/tests/UnusedCodeTest.php @@ -738,7 +738,7 @@ public function unserialize($_serialized) : void {} new Foo();' ], 'ignoreSerializeAndUnserialize' => [ - ' ' Date: Tue, 8 Nov 2022 10:55:14 +0100 Subject: [PATCH 38/41] Mark as internal --- src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php index cea996b799e..7787ca0a660 100644 --- a/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php +++ b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php @@ -9,6 +9,7 @@ use Psalm\Type\TypeVisitor; use Psalm\Type\Union; +/** @internal */ class CanContainObjectTypeVisitor extends TypeVisitor { /** From c720e41469c7f51b21b3b316bec0077d2629834b Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 8 Nov 2022 16:00:00 +0100 Subject: [PATCH 39/41] Better docs --- .../annotating_code/type_syntax/array_types.md | 12 +++++++----- .../type_syntax/object_types.md | 8 +++++--- .../type_syntax/scalar_types.md | 2 ++ .../type_syntax/utility_types.md | 18 ++++++++++-------- .../annotating_code/type_syntax/value_types.md | 10 ++++++---- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/docs/annotating_code/type_syntax/array_types.md b/docs/annotating_code/type_syntax/array_types.md index 1a94f0bdbb4..3f8542396af 100644 --- a/docs/annotating_code/type_syntax/array_types.md +++ b/docs/annotating_code/type_syntax/array_types.md @@ -25,15 +25,17 @@ PHP treats all these arrays the same, essentially (though there are some optimis Psalm has a few different ways to represent arrays in its type system: -- [array<int, string>](#generic-arrays) -- [non-empty-array](#non-empty-array) -- [string\[\]](#phpdoc-syntax) -- [list & non-empty-list](#lists) +_Click on the » next to each type to view detailed documentation and examples._ + +- [array<int, string> »](#generic-arrays) +- [non-empty-array »](#non-empty-array) +- [string\[\] »](#phpdoc-syntax) +- [list & non-empty-list »](#lists) - [list<string>](#lists) - [array{foo: int, bar: string} and list{int, string} »](#object-like-arrays) - [Sealed arrays »](#sealed-object-like-arrays) - [Unsealed arrays »](#unsealed-object-like-arrays) -- [callable-array](#callable-arrays) +- [callable-array »](#callable-arrays) ## Generic arrays diff --git a/docs/annotating_code/type_syntax/object_types.md b/docs/annotating_code/type_syntax/object_types.md index eca8397ae54..33e6a63fb73 100644 --- a/docs/annotating_code/type_syntax/object_types.md +++ b/docs/annotating_code/type_syntax/object_types.md @@ -1,8 +1,10 @@ # Object types -- [object](#unnamed-objects) -- [object{foo: string}](#object-properties) -- [Exception, Foo\MyClass and Foo\MyClass](#named-objectsmd) +_Click on the » next to each type to view detailed documentation and examples._ + +- [object »](#unnamed-objects) +- [object{foo: string} »](#object-properties) +- [Exception, Foo\MyClass and Foo\MyClass<Bar> »](#named-objects) - [Generator](#generators) ### Unnamed objects diff --git a/docs/annotating_code/type_syntax/scalar_types.md b/docs/annotating_code/type_syntax/scalar_types.md index 92e8d69d317..b202294d570 100644 --- a/docs/annotating_code/type_syntax/scalar_types.md +++ b/docs/annotating_code/type_syntax/scalar_types.md @@ -1,5 +1,7 @@ # Scalar types +_Click on the » next to each type to view detailed documentation and examples._ + * [bool »](scalar_types.md#scalar) * [int »](scalar_types.md#scalar) * [float »](scalar_types.md#scalar) diff --git a/docs/annotating_code/type_syntax/utility_types.md b/docs/annotating_code/type_syntax/utility_types.md index 8b3fef940cc..6f6670d5802 100644 --- a/docs/annotating_code/type_syntax/utility_types.md +++ b/docs/annotating_code/type_syntax/utility_types.md @@ -2,14 +2,16 @@ Psalm supports some _magical_ utility types that brings superpower to the PHP type system. -- [(T is true ? string : bool)](conditional_types.md) -- [`key-of`](#key-oft) -- [`value-of`](#value-oft) -- [`properties-of`](#properties-oft) -- [`class-string-map`](#class-string-mapt-as-foo-t) -- [`T[K]`](#tk) -- [Type aliases](#type-aliases) -- [Variable templates](#variable-templates) +_Click on the » next to each type to view detailed documentation and examples._ + +- [(T is true ? string : bool) »](conditional_types.md) +- [`key-of` »](#key-oft) +- [`value-of` »](#value-oft) +- [`properties-of` »](#properties-oft) +- [`class-string-map` »](#class-string-mapt-as-foo-t) +- [`T[K]` »](#tk) +- [Type aliases »](#type-aliases) +- [Variable templates »](#variable-templates) ## `key-of` diff --git a/docs/annotating_code/type_syntax/value_types.md b/docs/annotating_code/type_syntax/value_types.md index a9bb86caa14..610274202a2 100644 --- a/docs/annotating_code/type_syntax/value_types.md +++ b/docs/annotating_code/type_syntax/value_types.md @@ -2,10 +2,12 @@ Psalm also allows you to specify values in types. -- [null](#null) -- [true, false](#true-false) -- [6, 7.0, "forty-two" and 'forty two'](#some_string-4-314) -- [Foo\Bar::MY_SCALAR_CONST](#regular-class-constants) +_Click on the » next to each type to view detailed documentation and examples._ + +- [null »](#null) +- [true, false »](#true-false) +- [6, 7.0, "forty-two" and 'forty two' »](#some_string-4-314) +- [Foo\Bar::MY_SCALAR_CONST »](#regular-class-constants) ### null From a341863db2e73b6c2c81515a815210d10752f9e8 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 8 Nov 2022 17:54:03 +0100 Subject: [PATCH 40/41] Fix typo --- docs/annotating_code/type_syntax/array_types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/annotating_code/type_syntax/array_types.md b/docs/annotating_code/type_syntax/array_types.md index 3f8542396af..81ff64ff62f 100644 --- a/docs/annotating_code/type_syntax/array_types.md +++ b/docs/annotating_code/type_syntax/array_types.md @@ -193,7 +193,7 @@ avgShape([123.1, 321.0, 1.0, new class {}, 'test']); The above examples contain bugs which can be detected by Psalm *only when using sealed arrays*. -The counterpart to sealed arrays are [unsealed arrays &rauo;](#unsealed-object-like-arrays), generated as intermediate types when asserting raw arrays. +The counterpart to sealed arrays are [unsealed arrays »](#unsealed-object-like-arrays), generated as intermediate types when asserting raw arrays. Unsealed arrays are by definition uncertain, so Psalm can't reason well about them: always convert them to sealed arrays as specified [here »](#unsealed-object-like-arrays). Tip: if you find yourself copying the same complex sealed array shape over and over again to avoid `InvalidArgument` issues, try using [type aliases](utility_types.md#type-aliases), instead. From 0d0e96504246f0c230a4d58e26ca96971b0c01ef Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 8 Nov 2022 17:54:43 +0100 Subject: [PATCH 41/41] Fix typo --- docs/annotating_code/type_syntax/array_types.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/annotating_code/type_syntax/array_types.md b/docs/annotating_code/type_syntax/array_types.md index 81ff64ff62f..5e3b1b519e3 100644 --- a/docs/annotating_code/type_syntax/array_types.md +++ b/docs/annotating_code/type_syntax/array_types.md @@ -276,7 +276,6 @@ function avgCoefficient(array $params): float { You can also manually provide a `['a' => $arr['a'], 'b' => $arr['b']]`, but there's an even better way to seamlessly validate user-provided input: - + Tip: if you find yourself copying the same complex sealed array shape over and over again to avoid `InvalidArgument` issues, try using [type aliases](utility_types.md#type-aliases), instead.