diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index dc2d5e12d..edb5aaa88 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -619,8 +619,10 @@ class FunctionCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expressio || $codebase->find_unused_variables || !$config->remember_property_assignments_after_call) ) { + $must_use = false; + $callmap_function_pure = $function_id && $in_call_map - ? $codebase->functions->isCallMapFunctionPure($codebase, $function_id, $stmt->args) + ? $codebase->functions->isCallMapFunctionPure($codebase, $function_id, $stmt->args, $must_use) : null; if (($function_storage @@ -643,7 +645,8 @@ class FunctionCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expressio $context->removeAllObjectVars(); } } elseif ($function_id - && (($function_storage && $function_storage->pure) || $callmap_function_pure === true) + && (($function_storage && $function_storage->pure) + || ($callmap_function_pure === true && $must_use)) && $codebase->find_unused_variables && !$context->inside_conditional && !$context->inside_unset diff --git a/src/Psalm/Internal/Codebase/Functions.php b/src/Psalm/Internal/Codebase/Functions.php index 883f32869..e2c3564a9 100644 --- a/src/Psalm/Internal/Codebase/Functions.php +++ b/src/Psalm/Internal/Codebase/Functions.php @@ -264,8 +264,12 @@ class Functions /** * @param array $args */ - public function isCallMapFunctionPure(Codebase $codebase, string $function_id, array $args) : bool - { + public function isCallMapFunctionPure( + Codebase $codebase, + string $function_id, + array $args, + bool &$must_use + ) : bool { $impure_functions = [ // file io 'chdir', 'chgrp', 'chmod', 'chown', 'chroot', 'closedir', 'copy', 'file_put_contents', @@ -309,7 +313,7 @@ class Functions 'shell_exec', 'exec', 'system', 'passthru', 'pcntl_exec', // well-known functions - 'libxml_use_internal_errors', 'array_map', 'curl_exec', + 'libxml_use_internal_errors', 'curl_exec', 'mt_srand', 'openssl_pkcs7_sign', 'mysqli_select_db', 'preg_replace_callback', 'mt_rand', 'rand', @@ -346,11 +350,6 @@ class Functions return false; } - // $matches is basically the (conditional) output of these functions - if ($function_id === 'preg_match' || $function_id === 'preg_match_all') { - return true; - } - $function_callable = \Psalm\Internal\Codebase\CallMap::getCallableFromCallMapById( $codebase, $function_id, @@ -361,10 +360,16 @@ class Functions return false; } + $must_use = true; + foreach ($function_callable->params as $i => $param) { - if ($param->by_ref && isset($args[$i])) { + if ($param->type && $param->type->hasCallableType() && isset($args[$i])) { return false; } + + if ($param->by_ref && isset($args[$i])) { + $must_use = false; + } } return true;