diff --git a/src/Psalm/Checker/Statements/Expression/Call/FunctionCallChecker.php b/src/Psalm/Checker/Statements/Expression/Call/FunctionCallChecker.php index 4a7ea6f18..031bc6a15 100644 --- a/src/Psalm/Checker/Statements/Expression/Call/FunctionCallChecker.php +++ b/src/Psalm/Checker/Statements/Expression/Call/FunctionCallChecker.php @@ -216,6 +216,9 @@ class FunctionCallChecker extends \Psalm\Checker\Statements\Expression\CallCheck $is_predefined = true; + $is_maybe_root_function = !$stmt->name instanceof PhpParser\Node\Name\FullyQualified + && count($stmt->name->parts) === 1; + if (!$in_call_map) { $predefined_functions = $config->getPredefinedFunctions(); $is_predefined = isset($predefined_functions[$function_id]); @@ -233,13 +236,18 @@ class FunctionCallChecker extends \Psalm\Checker\Statements\Expression\CallCheck if (self::checkFunctionExists( $statements_checker, $function_id, - $code_location + $code_location, + $is_maybe_root_function ) === false ) { return false; } } else { - $function_id = self::getExistingFunctionId($statements_checker, $function_id); + $function_id = self::getExistingFunctionId( + $statements_checker, + $function_id, + $is_maybe_root_function + ); } $function_exists = $is_stubbed || $codebase_functions->functionExists( diff --git a/src/Psalm/Checker/Statements/Expression/CallChecker.php b/src/Psalm/Checker/Statements/Expression/CallChecker.php index 110596de4..df7a0f937 100644 --- a/src/Psalm/Checker/Statements/Expression/CallChecker.php +++ b/src/Psalm/Checker/Statements/Expression/CallChecker.php @@ -1269,7 +1269,8 @@ class CallChecker if (self::checkFunctionExists( $statements_checker, $function_id, - $code_location + $code_location, + false ) === false ) { return false; @@ -1377,13 +1378,15 @@ class CallChecker * @param StatementsChecker $statements_checker * @param string $function_id * @param CodeLocation $code_location + * @param bool $can_be_in_root_scope if true, the function can be shortened to the root version * * @return bool */ protected static function checkFunctionExists( StatementsChecker $statements_checker, &$function_id, - CodeLocation $code_location + CodeLocation $code_location, + $can_be_in_root_scope ) { $cased_function_id = $function_id; $function_id = strtolower($function_id); @@ -1393,7 +1396,8 @@ class CallChecker if (!$codebase->functions->functionExists($statements_checker, $function_id)) { $root_function_id = preg_replace('/.*\\\/', '', $function_id); - if ($function_id !== $root_function_id + if ($can_be_in_root_scope + && $function_id !== $root_function_id && $codebase->functions->functionExists($statements_checker, $root_function_id) ) { $function_id = $root_function_id; @@ -1418,11 +1422,15 @@ class CallChecker /** * @param StatementsChecker $statements_checker * @param string $function_id + * @param bool $can_be_in_root_scope if true, the function can be shortened to the root version * * @return string */ - protected static function getExistingFunctionId(StatementsChecker $statements_checker, $function_id) - { + protected static function getExistingFunctionId( + StatementsChecker $statements_checker, + $function_id, + $can_be_in_root_scope + ) { $function_id = strtolower($function_id); $codebase = $statements_checker->getFileChecker()->project_checker->codebase; @@ -1431,6 +1439,10 @@ class CallChecker return $function_id; } + if (!$can_be_in_root_scope) { + return $function_id; + } + $root_function_id = preg_replace('/.*\\\/', '', $function_id); if ($function_id !== $root_function_id diff --git a/tests/NamespaceTest.php b/tests/NamespaceTest.php index 7db1f9d67..daab95905 100644 --- a/tests/NamespaceTest.php +++ b/tests/NamespaceTest.php @@ -4,6 +4,7 @@ namespace Psalm\Tests; class NamespaceTest extends TestCase { use Traits\FileCheckerValidCodeParseTestTrait; + use Traits\FileCheckerInvalidCodeParseTestTrait; /** * @return array @@ -16,17 +17,17 @@ class NamespaceTest extends TestCase namespace A { /** @return void */ function foo() { - + } - + class Bar { - + } } namespace { A\foo(); \A\foo(); - + (new A\Bar); }', ], @@ -40,7 +41,7 @@ class NamespaceTest extends TestCase function foo() { echo \Aye\Bee\HELLO; } - + class Bar { /** @return void */ public function foo() { @@ -51,4 +52,39 @@ class NamespaceTest extends TestCase ], ]; } + + /** + * @return array + */ + public function providerFileCheckerInvalidCodeParse() + { + return [ + 'callNamespacedFunctionFromEmptyNamespace' => [ + ' 'UndefinedFunction', + ], + 'callRootFunctionFromNamespace' => [ + ' 'UndefinedFunction', + ], + ]; + } }