2018-02-04 00:52:35 +01:00
|
|
|
<?php
|
2018-11-12 16:46:55 +01:00
|
|
|
namespace Psalm\Internal\Codebase;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Codebase;
|
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
2019-03-01 14:57:10 +01:00
|
|
|
use Psalm\Internal\Provider\{
|
|
|
|
FileStorageProvider,
|
|
|
|
FunctionReturnTypeProvider,
|
|
|
|
FunctionExistenceProvider,
|
|
|
|
FunctionParamsProvider
|
|
|
|
};
|
2018-02-04 00:52:35 +01:00
|
|
|
use Psalm\StatementsSource;
|
|
|
|
use Psalm\Storage\FunctionLikeStorage;
|
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2018-02-04 00:52:35 +01:00
|
|
|
class Functions
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var FileStorageProvider
|
|
|
|
*/
|
|
|
|
private $file_storage_provider;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, FunctionLikeStorage>
|
|
|
|
*/
|
|
|
|
private static $stubbed_functions;
|
|
|
|
|
2019-02-16 00:00:40 +01:00
|
|
|
/** @var FunctionReturnTypeProvider */
|
|
|
|
public $return_type_provider;
|
|
|
|
|
2019-03-01 14:57:10 +01:00
|
|
|
/** @var FunctionExistenceProvider */
|
|
|
|
public $existence_provider;
|
|
|
|
|
|
|
|
/** @var FunctionParamsProvider */
|
|
|
|
public $params_provider;
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
|
|
|
* @var Reflection
|
|
|
|
*/
|
|
|
|
private $reflection;
|
|
|
|
|
|
|
|
public function __construct(FileStorageProvider $storage_provider, Reflection $reflection)
|
|
|
|
{
|
|
|
|
$this->file_storage_provider = $storage_provider;
|
|
|
|
$this->reflection = $reflection;
|
2019-02-16 00:00:40 +01:00
|
|
|
$this->return_type_provider = new FunctionReturnTypeProvider();
|
2019-03-01 14:57:10 +01:00
|
|
|
$this->existence_provider = new FunctionExistenceProvider();
|
|
|
|
$this->params_provider = new FunctionParamsProvider();
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
self::$stubbed_functions = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-11-11 18:01:14 +01:00
|
|
|
* @param StatementsAnalyzer|null $statements_analyzer
|
2018-02-04 00:52:35 +01:00
|
|
|
* @param string $function_id
|
|
|
|
*
|
|
|
|
* @return FunctionLikeStorage
|
|
|
|
*/
|
2018-11-11 18:01:14 +01:00
|
|
|
public function getStorage($statements_analyzer, $function_id)
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
2018-02-21 17:32:52 +01:00
|
|
|
if (isset(self::$stubbed_functions[strtolower($function_id)])) {
|
|
|
|
return self::$stubbed_functions[strtolower($function_id)];
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->reflection->hasFunction($function_id)) {
|
|
|
|
return $this->reflection->getFunctionStorage($function_id);
|
|
|
|
}
|
2018-03-13 23:11:57 +01:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
if (!$statements_analyzer) {
|
|
|
|
throw new \UnexpectedValueException('$statements_analyzer must not be null here');
|
2018-03-13 23:11:57 +01:00
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$file_path = $statements_analyzer->getRootFilePath();
|
|
|
|
$checked_file_path = $statements_analyzer->getFilePath();
|
2018-02-04 00:52:35 +01:00
|
|
|
$file_storage = $this->file_storage_provider->get($file_path);
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$function_analyzers = $statements_analyzer->getFunctionAnalyzers();
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
if (isset($function_analyzers[$function_id])) {
|
|
|
|
$function_id = $function_analyzers[$function_id]->getMethodId();
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-05-23 05:38:27 +02:00
|
|
|
if (isset($file_storage->functions[$function_id])) {
|
|
|
|
return $file_storage->functions[$function_id];
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// closures can be returned here
|
|
|
|
if (isset($file_storage->functions[$function_id])) {
|
|
|
|
return $file_storage->functions[$function_id];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($file_storage->declaring_function_ids[$function_id])) {
|
2018-06-07 03:47:26 +02:00
|
|
|
if ($checked_file_path !== $file_path) {
|
|
|
|
$file_storage = $this->file_storage_provider->get($checked_file_path);
|
|
|
|
|
|
|
|
if (isset($file_storage->functions[$function_id])) {
|
|
|
|
return $file_storage->functions[$function_id];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
throw new \UnexpectedValueException(
|
|
|
|
'Expecting ' . $function_id . ' to have storage in ' . $file_path
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$declaring_file_path = $file_storage->declaring_function_ids[$function_id];
|
|
|
|
|
|
|
|
$declaring_file_storage = $this->file_storage_provider->get($declaring_file_path);
|
|
|
|
|
|
|
|
if (!isset($declaring_file_storage->functions[$function_id])) {
|
|
|
|
throw new \UnexpectedValueException(
|
|
|
|
'Not expecting ' . $function_id . ' to not have storage in ' . $declaring_file_path
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $declaring_file_storage->functions[$function_id];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $function_id
|
|
|
|
* @param FunctionLikeStorage $storage
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2018-06-30 21:29:37 +02:00
|
|
|
public function addGlobalFunction($function_id, FunctionLikeStorage $storage)
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
2018-02-21 17:32:52 +01:00
|
|
|
self::$stubbed_functions[strtolower($function_id)] = $storage;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $function_id
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function hasStubbedFunction($function_id)
|
|
|
|
{
|
2018-02-21 17:32:52 +01:00
|
|
|
return isset(self::$stubbed_functions[strtolower($function_id)]);
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
2019-03-01 14:57:10 +01:00
|
|
|
public function functionExists(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
string $function_id
|
|
|
|
) {
|
|
|
|
if ($this->existence_provider->has($function_id)) {
|
|
|
|
$function_exists = $this->existence_provider->doesFunctionExist($statements_analyzer, $function_id);
|
|
|
|
|
|
|
|
if ($function_exists !== null) {
|
|
|
|
return $function_exists;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$file_storage = $this->file_storage_provider->get($statements_analyzer->getRootFilePath());
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
if (isset($file_storage->declaring_function_ids[$function_id])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->reflection->hasFunction($function_id)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-02-21 17:32:52 +01:00
|
|
|
if (isset(self::$stubbed_functions[strtolower($function_id)])) {
|
2018-02-04 00:52:35 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
if (isset($statements_analyzer->getFunctionAnalyzers()[$function_id])) {
|
2018-02-04 00:52:35 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-01-20 01:49:58 +01:00
|
|
|
$predefined_functions = $statements_analyzer->getCodebase()->config->getPredefinedFunctions();
|
|
|
|
|
|
|
|
if (isset($predefined_functions[$function_id])) {
|
2019-04-10 00:09:57 +02:00
|
|
|
/** @psalm-suppress TypeCoercion */
|
2019-01-20 01:49:58 +01:00
|
|
|
if ($this->reflection->registerFunction($function_id) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2019-01-20 01:49:58 +01:00
|
|
|
return false;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $function_name
|
|
|
|
* @param StatementsSource $source
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getFullyQualifiedFunctionNameFromString($function_name, StatementsSource $source)
|
|
|
|
{
|
|
|
|
if (empty($function_name)) {
|
|
|
|
throw new \InvalidArgumentException('$function_name cannot be empty');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($function_name[0] === '\\') {
|
|
|
|
return substr($function_name, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
$function_name_lcase = strtolower($function_name);
|
|
|
|
|
|
|
|
$aliases = $source->getAliases();
|
|
|
|
|
|
|
|
$imported_function_namespaces = $aliases->functions;
|
|
|
|
$imported_namespaces = $aliases->uses;
|
|
|
|
|
|
|
|
if (strpos($function_name, '\\') !== false) {
|
|
|
|
$function_name_parts = explode('\\', $function_name);
|
|
|
|
$first_namespace = array_shift($function_name_parts);
|
|
|
|
$first_namespace_lcase = strtolower($first_namespace);
|
|
|
|
|
|
|
|
if (isset($imported_namespaces[$first_namespace_lcase])) {
|
|
|
|
return $imported_namespaces[$first_namespace_lcase] . '\\' . implode('\\', $function_name_parts);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($imported_function_namespaces[$first_namespace_lcase])) {
|
|
|
|
return $imported_function_namespaces[$first_namespace_lcase] . '\\' .
|
|
|
|
implode('\\', $function_name_parts);
|
|
|
|
}
|
|
|
|
} elseif (isset($imported_function_namespaces[$function_name_lcase])) {
|
|
|
|
return $imported_function_namespaces[$function_name_lcase];
|
|
|
|
}
|
|
|
|
|
|
|
|
$namespace = $source->getNamespace();
|
|
|
|
|
|
|
|
return ($namespace ? $namespace . '\\' : '') . $function_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $function_id
|
|
|
|
* @param string $file_path
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public static function isVariadic(Codebase $codebase, $function_id, $file_path)
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
2018-11-06 03:57:36 +01:00
|
|
|
$file_storage = $codebase->file_storage_provider->get($file_path);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-03-17 20:02:25 +01:00
|
|
|
if (!isset($file_storage->declaring_function_ids[$function_id])) {
|
2018-03-17 20:28:41 +01:00
|
|
|
return false;
|
2018-03-17 20:02:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$declaring_file_path = $file_storage->declaring_function_ids[$function_id];
|
|
|
|
|
|
|
|
$file_storage = $declaring_file_path === $file_path
|
|
|
|
? $file_storage
|
2018-11-06 03:57:36 +01:00
|
|
|
: $codebase->file_storage_provider->get($declaring_file_path);
|
2018-03-17 20:02:25 +01:00
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
return isset($file_storage->functions[$function_id]) && $file_storage->functions[$function_id]->variadic;
|
|
|
|
}
|
2019-05-17 00:36:36 +02:00
|
|
|
|
|
|
|
public static function clearCache() : void
|
|
|
|
{
|
|
|
|
self::$stubbed_functions = [];
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|