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-05-20 18:47:18 +02:00
|
|
|
use PhpParser;
|
2019-03-14 15:11:45 +01:00
|
|
|
use Psalm\Codebase;
|
2018-02-04 00:52:35 +01:00
|
|
|
use Psalm\CodeLocation;
|
2019-03-01 14:57:10 +01:00
|
|
|
use Psalm\Internal\Provider\{
|
|
|
|
ClassLikeStorageProvider,
|
|
|
|
FileReferenceProvider,
|
|
|
|
MethodReturnTypeProvider,
|
|
|
|
MethodExistenceProvider,
|
|
|
|
MethodParamsProvider,
|
|
|
|
MethodVisibilityProvider
|
|
|
|
};
|
|
|
|
use Psalm\StatementsSource;
|
2019-01-11 14:54:10 +01:00
|
|
|
use Psalm\Storage\ClassLikeStorage;
|
2018-10-06 19:46:35 +02:00
|
|
|
use Psalm\Storage\FunctionLikeParameter;
|
2018-02-04 00:52:35 +01:00
|
|
|
use Psalm\Storage\MethodStorage;
|
|
|
|
use Psalm\Type;
|
|
|
|
|
2018-02-09 23:51:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*
|
|
|
|
* Handles information about class methods
|
|
|
|
*/
|
2018-02-04 00:52:35 +01:00
|
|
|
class Methods
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var ClassLikeStorageProvider
|
|
|
|
*/
|
|
|
|
private $classlike_storage_provider;
|
|
|
|
|
2018-04-22 05:08:08 +02:00
|
|
|
/**
|
|
|
|
* @var \Psalm\Config
|
|
|
|
*/
|
|
|
|
private $config;
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
public $collect_references = false;
|
|
|
|
|
2018-09-28 22:18:45 +02:00
|
|
|
/**
|
|
|
|
* @var FileReferenceProvider
|
|
|
|
*/
|
|
|
|
public $file_reference_provider;
|
|
|
|
|
2018-12-21 17:32:44 +01:00
|
|
|
/**
|
|
|
|
* @var ClassLikes
|
|
|
|
*/
|
|
|
|
private $classlikes;
|
|
|
|
|
2019-02-16 17:16:52 +01:00
|
|
|
/** @var MethodReturnTypeProvider */
|
|
|
|
public $return_type_provider;
|
|
|
|
|
2019-03-01 14:57:10 +01:00
|
|
|
/** @var MethodParamsProvider */
|
|
|
|
public $params_provider;
|
|
|
|
|
|
|
|
/** @var MethodExistenceProvider */
|
|
|
|
public $existence_provider;
|
|
|
|
|
|
|
|
/** @var MethodVisibilityProvider */
|
|
|
|
public $visibility_provider;
|
|
|
|
|
2018-02-09 00:14:28 +01:00
|
|
|
/**
|
|
|
|
* @param ClassLikeStorageProvider $storage_provider
|
|
|
|
*/
|
2018-02-04 00:52:35 +01:00
|
|
|
public function __construct(
|
2018-04-22 05:08:08 +02:00
|
|
|
\Psalm\Config $config,
|
2018-09-28 22:18:45 +02:00
|
|
|
ClassLikeStorageProvider $storage_provider,
|
2018-12-21 17:32:44 +01:00
|
|
|
FileReferenceProvider $file_reference_provider,
|
|
|
|
ClassLikes $classlikes
|
2018-02-04 00:52:35 +01:00
|
|
|
) {
|
|
|
|
$this->classlike_storage_provider = $storage_provider;
|
2018-04-22 05:08:08 +02:00
|
|
|
$this->config = $config;
|
2018-09-28 22:18:45 +02:00
|
|
|
$this->file_reference_provider = $file_reference_provider;
|
2018-12-21 17:32:44 +01:00
|
|
|
$this->classlikes = $classlikes;
|
2019-02-16 17:16:52 +01:00
|
|
|
$this->return_type_provider = new MethodReturnTypeProvider();
|
2019-03-01 14:57:10 +01:00
|
|
|
$this->existence_provider = new MethodExistenceProvider();
|
|
|
|
$this->visibility_provider = new MethodVisibilityProvider();
|
|
|
|
$this->params_provider = new MethodParamsProvider();
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether or not a given method exists
|
|
|
|
*
|
|
|
|
* @param string $method_id
|
2018-09-27 19:32:08 +02:00
|
|
|
* @param ?string $calling_method_id
|
2018-02-04 00:52:35 +01:00
|
|
|
* @param CodeLocation|null $code_location
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function methodExists(
|
2018-09-27 19:32:08 +02:00
|
|
|
$method_id,
|
|
|
|
$calling_method_id = null,
|
2019-03-01 14:57:10 +01:00
|
|
|
CodeLocation $code_location = null,
|
|
|
|
StatementsSource $source = null
|
2018-02-04 00:52:35 +01:00
|
|
|
) {
|
|
|
|
// remove trailing backslash if it exists
|
|
|
|
$method_id = preg_replace('/^\\\\/', '', $method_id);
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
$method_name = strtolower($method_name);
|
|
|
|
$method_id = $fq_class_name . '::' . $method_name;
|
|
|
|
|
2019-03-01 14:57:10 +01:00
|
|
|
if ($this->existence_provider->has($fq_class_name)) {
|
|
|
|
$method_exists = $this->existence_provider->doesMethodExist(
|
|
|
|
$fq_class_name,
|
|
|
|
$method_name,
|
|
|
|
$source,
|
|
|
|
$code_location
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($method_exists !== null) {
|
|
|
|
return $method_exists;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
$old_method_id = null;
|
|
|
|
|
2018-12-21 17:32:44 +01:00
|
|
|
$fq_class_name = $this->classlikes->getUnAliasedName($fq_class_name);
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
if (isset($class_storage->declaring_method_ids[$method_name])) {
|
2018-09-26 00:37:24 +02:00
|
|
|
$declaring_method_id = $class_storage->declaring_method_ids[$method_name];
|
|
|
|
|
|
|
|
$declaring_method_id_lc = strtolower($declaring_method_id);
|
|
|
|
|
|
|
|
if ($calling_method_id === $declaring_method_id_lc) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($calling_method_id) {
|
|
|
|
$method_id_lc = strtolower($method_id);
|
|
|
|
|
|
|
|
if ($method_id_lc !== $declaring_method_id_lc
|
|
|
|
&& $class_storage->user_defined
|
|
|
|
&& isset($class_storage->potential_declaring_method_ids[$method_name])
|
|
|
|
) {
|
|
|
|
foreach ($class_storage->potential_declaring_method_ids[$method_name] as $potential_id => $_) {
|
2018-09-28 22:18:45 +02:00
|
|
|
$this->file_reference_provider->addReferenceToClassMethod(
|
2018-09-26 00:37:24 +02:00
|
|
|
$calling_method_id,
|
|
|
|
$potential_id
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
2018-09-28 22:18:45 +02:00
|
|
|
$this->file_reference_provider->addReferenceToClassMethod(
|
2018-09-26 00:37:24 +02:00
|
|
|
$calling_method_id,
|
|
|
|
$declaring_method_id_lc
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
if ($this->collect_references && $code_location) {
|
|
|
|
list($declaring_method_class, $declaring_method_name) = explode('::', $declaring_method_id);
|
|
|
|
|
|
|
|
$declaring_class_storage = $this->classlike_storage_provider->get($declaring_method_class);
|
|
|
|
$declaring_method_storage = $declaring_class_storage->methods[strtolower($declaring_method_name)];
|
|
|
|
if ($declaring_method_storage->referencing_locations === null) {
|
|
|
|
$declaring_method_storage->referencing_locations = [];
|
|
|
|
}
|
|
|
|
$declaring_method_storage->referencing_locations[$code_location->file_path][] = $code_location;
|
|
|
|
|
|
|
|
foreach ($class_storage->class_implements as $fq_interface_name) {
|
|
|
|
$interface_storage = $this->classlike_storage_provider->get($fq_interface_name);
|
|
|
|
if (isset($interface_storage->methods[$method_name])) {
|
|
|
|
$interface_method_storage = $interface_storage->methods[$method_name];
|
|
|
|
if (!isset($interface_method_storage->referencing_locations)) {
|
|
|
|
$interface_method_storage->referencing_locations = [];
|
|
|
|
}
|
|
|
|
$interface_method_storage->referencing_locations[$code_location->file_path][] = $code_location;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($declaring_class_storage->overridden_method_ids[$declaring_method_name])) {
|
|
|
|
$overridden_method_ids = $declaring_class_storage->overridden_method_ids[$declaring_method_name];
|
|
|
|
|
|
|
|
foreach ($overridden_method_ids as $overridden_method_id) {
|
|
|
|
list($overridden_method_class, $overridden_method_name) = explode('::', $overridden_method_id);
|
|
|
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($overridden_method_class);
|
|
|
|
$method_storage = $class_storage->methods[strtolower($overridden_method_name)];
|
|
|
|
if ($method_storage->referencing_locations === null) {
|
|
|
|
$method_storage->referencing_locations = [];
|
|
|
|
}
|
|
|
|
$method_storage->referencing_locations[$code_location->file_path][] = $code_location;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$method_name])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// support checking oldstyle constructors
|
|
|
|
if ($method_name === '__construct') {
|
|
|
|
$method_name_parts = explode('\\', $fq_class_name);
|
|
|
|
$old_constructor_name = array_pop($method_name_parts);
|
|
|
|
$old_method_id = $fq_class_name . '::' . $old_constructor_name;
|
|
|
|
}
|
|
|
|
|
2018-02-22 19:42:34 +01:00
|
|
|
if (!$class_storage->user_defined
|
|
|
|
&& (CallMap::inCallMap($method_id) || ($old_method_id && CallMap::inCallMap($method_id)))
|
|
|
|
) {
|
2018-02-04 00:52:35 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
if ($calling_method_id) {
|
|
|
|
// also store failures in case the method is added later
|
2018-09-28 22:18:45 +02:00
|
|
|
$this->file_reference_provider->addReferenceToClassMethod(
|
2018-09-26 00:37:24 +02:00
|
|
|
$calling_method_id,
|
|
|
|
strtolower($method_id)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
2019-03-01 14:57:10 +01:00
|
|
|
* @param array<PhpParser\Node\Arg> $args
|
2018-02-04 00:52:35 +01:00
|
|
|
*
|
2018-10-06 19:46:35 +02:00
|
|
|
* @return array<int, FunctionLikeParameter>
|
2018-02-04 00:52:35 +01:00
|
|
|
*/
|
2019-03-01 14:57:10 +01:00
|
|
|
public function getMethodParams($method_id, StatementsSource $source = null, array $args = null)
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
2019-03-01 14:57:10 +01:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
if ($this->params_provider->has($fq_class_name)) {
|
|
|
|
$method_params = $this->params_provider->getMethodParams(
|
|
|
|
$fq_class_name,
|
|
|
|
$method_name,
|
|
|
|
$args,
|
|
|
|
$source
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($method_params !== null) {
|
|
|
|
return $method_params;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-24 21:03:13 +01:00
|
|
|
if ($declaring_method_id = $this->getDeclaringMethodId($method_id)) {
|
|
|
|
$storage = $this->getStorage($declaring_method_id);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-01-19 18:30:14 +01:00
|
|
|
if ($storage->inheritdoc) {
|
2019-01-19 18:19:07 +01:00
|
|
|
$non_null_param_types = array_filter(
|
|
|
|
$storage->params,
|
|
|
|
/** @return bool */
|
|
|
|
function (FunctionLikeParameter $p) {
|
2019-01-19 18:42:46 +01:00
|
|
|
return $p->type !== null && $p->has_docblock_type;
|
2019-01-19 18:19:07 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
} else {
|
2019-01-19 18:30:14 +01:00
|
|
|
$non_null_param_types = array_filter(
|
|
|
|
$storage->params,
|
|
|
|
/** @return bool */
|
|
|
|
function (FunctionLikeParameter $p) {
|
|
|
|
return $p->type !== null;
|
|
|
|
}
|
|
|
|
);
|
2019-01-19 18:19:07 +01:00
|
|
|
}
|
2018-10-06 19:46:35 +02:00
|
|
|
|
|
|
|
$params = $storage->params;
|
|
|
|
|
|
|
|
if ($non_null_param_types) {
|
|
|
|
return $params;
|
|
|
|
}
|
|
|
|
|
2019-01-24 21:03:13 +01:00
|
|
|
$appearing_method_id = $this->getAppearingMethodId($declaring_method_id);
|
2018-10-06 19:46:35 +02:00
|
|
|
|
|
|
|
if (!$appearing_method_id) {
|
|
|
|
return $params;
|
|
|
|
}
|
|
|
|
|
|
|
|
list($appearing_fq_class_name, $appearing_method_name) = explode('::', $appearing_method_id);
|
|
|
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name);
|
|
|
|
|
|
|
|
if (!isset($class_storage->overridden_method_ids[$appearing_method_name])) {
|
|
|
|
return $params;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($class_storage->overridden_method_ids[$appearing_method_name] as $overridden_method_id) {
|
|
|
|
$overridden_storage = $this->getStorage($overridden_method_id);
|
|
|
|
|
2019-03-14 15:11:45 +01:00
|
|
|
list($overriding_fq_class_name) = explode('::', $overridden_method_id);
|
|
|
|
|
2018-10-06 19:46:35 +02:00
|
|
|
$non_null_param_types = array_filter(
|
|
|
|
$overridden_storage->params,
|
|
|
|
/** @return bool */
|
|
|
|
function (FunctionLikeParameter $p) {
|
|
|
|
return $p->type !== null && $p->type->from_docblock;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($non_null_param_types) {
|
|
|
|
foreach ($params as $i => $param) {
|
|
|
|
if (isset($overridden_storage->params[$i]->type)
|
|
|
|
&& $overridden_storage->params[$i]->type->from_docblock
|
|
|
|
&& $overridden_storage->params[$i]->name === $param->name
|
|
|
|
) {
|
|
|
|
$params[$i] = clone $param;
|
2019-03-14 15:11:45 +01:00
|
|
|
$params[$i]->type = clone $overridden_storage->params[$i]->type;
|
|
|
|
|
|
|
|
if ($params[$i]->type && $source) {
|
|
|
|
self::localizeParamType(
|
|
|
|
$source->getCodebase(),
|
|
|
|
$params[$i]->type,
|
|
|
|
$appearing_fq_class_name,
|
|
|
|
$overriding_fq_class_name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-10-06 19:46:35 +02:00
|
|
|
$params[$i]->type_location = $overridden_storage->params[$i]->type_location;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $params;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
throw new \UnexpectedValueException('Cannot get method params for ' . $method_id);
|
|
|
|
}
|
|
|
|
|
2019-03-14 15:11:45 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
private static function localizeParamType(
|
|
|
|
Codebase $codebase,
|
|
|
|
Type\Union $type,
|
|
|
|
string $appearing_fq_class_name,
|
|
|
|
string $base_fq_class_name
|
|
|
|
) {
|
|
|
|
$class_storage = $codebase->classlike_storage_provider->get($appearing_fq_class_name);
|
|
|
|
$extends = $class_storage->template_type_extends;
|
|
|
|
|
|
|
|
if (!$extends) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($type->getTypes() as $key => $atomic_type) {
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TTemplateParam
|
|
|
|
|| $atomic_type instanceof Type\Atomic\TTemplateParamClass
|
|
|
|
) {
|
|
|
|
if ($atomic_type->defining_class
|
|
|
|
&& strcasecmp($atomic_type->defining_class, $base_fq_class_name) === 0
|
|
|
|
) {
|
|
|
|
if (isset($extends[strtolower($base_fq_class_name)][$atomic_type->param_name])) {
|
|
|
|
$extended_param = $extends[strtolower($base_fq_class_name)][$atomic_type->param_name];
|
|
|
|
|
|
|
|
$type->removeType($key);
|
|
|
|
$type->addType($extended_param);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TArray
|
|
|
|
|| $atomic_type instanceof Type\Atomic\TIterable
|
|
|
|
|| $atomic_type instanceof Type\Atomic\TGenericObject
|
|
|
|
) {
|
|
|
|
foreach ($atomic_type->type_params as $type_param) {
|
|
|
|
self::localizeParamType(
|
|
|
|
$codebase,
|
|
|
|
$type_param,
|
|
|
|
$appearing_fq_class_name,
|
|
|
|
$base_fq_class_name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TCallable
|
|
|
|
|| $atomic_type instanceof Type\Atomic\Fn
|
|
|
|
) {
|
|
|
|
if ($atomic_type->params) {
|
|
|
|
foreach ($atomic_type->params as $param) {
|
|
|
|
if ($param->type) {
|
|
|
|
self::localizeParamType(
|
|
|
|
$codebase,
|
|
|
|
$param->type,
|
|
|
|
$appearing_fq_class_name,
|
|
|
|
$base_fq_class_name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type->return_type) {
|
|
|
|
self::localizeParamType(
|
|
|
|
$codebase,
|
|
|
|
$atomic_type->return_type,
|
|
|
|
$appearing_fq_class_name,
|
|
|
|
$base_fq_class_name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isVariadic($method_id)
|
|
|
|
{
|
2019-03-01 14:57:10 +01:00
|
|
|
$declaring_method_id = $this->getDeclaringMethodId($method_id);
|
|
|
|
|
|
|
|
if (!$declaring_method_id) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$method_id = $declaring_method_id;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
return $this->classlike_storage_provider->get($fq_class_name)->methods[$method_name]->variadic;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string $self_class
|
2018-05-20 18:47:18 +02:00
|
|
|
* @param array<int, PhpParser\Node\Arg>|null $args
|
2018-02-04 00:52:35 +01:00
|
|
|
*
|
|
|
|
* @return Type\Union|null
|
|
|
|
*/
|
2018-05-20 18:47:18 +02:00
|
|
|
public function getMethodReturnType($method_id, &$self_class, array $args = null)
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
2018-11-30 21:13:25 +01:00
|
|
|
$checked_for_pseudo_method = false;
|
|
|
|
|
|
|
|
if ($this->config->use_phpdoc_method_without_magic_or_parent) {
|
2018-04-22 05:08:08 +02:00
|
|
|
list($original_fq_class_name, $original_method_name) = explode('::', $method_id);
|
|
|
|
|
2018-12-21 17:32:44 +01:00
|
|
|
$original_fq_class_name = $this->classlikes->getUnAliasedName($original_fq_class_name);
|
|
|
|
|
2018-04-22 05:08:08 +02:00
|
|
|
$original_class_storage = $this->classlike_storage_provider->get($original_fq_class_name);
|
|
|
|
|
|
|
|
if (isset($original_class_storage->pseudo_methods[strtolower($original_method_name)])) {
|
|
|
|
return $original_class_storage->pseudo_methods[strtolower($original_method_name)]->return_type;
|
|
|
|
}
|
2018-11-30 21:13:25 +01:00
|
|
|
|
|
|
|
$checked_for_pseudo_method = true;
|
2018-04-22 05:08:08 +02:00
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
$declaring_method_id = $this->getDeclaringMethodId($method_id);
|
|
|
|
|
|
|
|
if (!$declaring_method_id) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-11-30 21:13:25 +01:00
|
|
|
if (!$checked_for_pseudo_method) {
|
|
|
|
list($original_fq_class_name, $original_method_name) = explode('::', $method_id);
|
|
|
|
|
2018-12-21 17:32:44 +01:00
|
|
|
$original_fq_class_name = $this->classlikes->getUnAliasedName($original_fq_class_name);
|
|
|
|
|
2018-11-30 21:13:25 +01:00
|
|
|
$original_class_storage = $this->classlike_storage_provider->get($original_fq_class_name);
|
|
|
|
|
|
|
|
if (isset($original_class_storage->pseudo_methods[strtolower($original_method_name)])) {
|
|
|
|
return $original_class_storage->pseudo_methods[strtolower($original_method_name)]->return_type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
$appearing_method_id = $this->getAppearingMethodId($method_id);
|
|
|
|
|
|
|
|
if (!$appearing_method_id) {
|
2018-11-11 01:05:51 +01:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
2018-12-21 17:32:44 +01:00
|
|
|
$fq_class_name = $this->classlikes->getUnAliasedName($fq_class_name);
|
|
|
|
|
2018-11-11 01:05:51 +01:00
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$method_name])) {
|
|
|
|
$appearing_method_id = $class_storage->overridden_method_ids[$method_name][0];
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
list($appearing_fq_class_name, $appearing_method_name) = explode('::', $appearing_method_id);
|
|
|
|
|
|
|
|
$appearing_fq_class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name);
|
|
|
|
|
|
|
|
if (!$appearing_fq_class_storage->user_defined && CallMap::inCallMap($appearing_method_id)) {
|
2018-05-20 18:47:18 +02:00
|
|
|
if ($appearing_method_id === 'Closure::fromcallable'
|
|
|
|
&& isset($args[0]->value->inferredType)
|
|
|
|
&& $args[0]->value->inferredType->isSingle()
|
|
|
|
) {
|
|
|
|
foreach ($args[0]->value->inferredType->getTypes() as $atomic_type) {
|
2019-01-21 23:08:12 +01:00
|
|
|
if ($atomic_type instanceof Type\Atomic\TCallable
|
|
|
|
|| $atomic_type instanceof Type\Atomic\Fn
|
|
|
|
) {
|
2018-05-20 18:47:18 +02:00
|
|
|
$callable_type = clone $atomic_type;
|
|
|
|
|
|
|
|
return new Type\Union([new Type\Atomic\Fn(
|
|
|
|
'Closure',
|
|
|
|
$callable_type->params,
|
|
|
|
$callable_type->return_type
|
|
|
|
)]);
|
|
|
|
}
|
2019-01-21 23:08:12 +01:00
|
|
|
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TNamedObject
|
|
|
|
&& $this->methodExists($atomic_type->value . '::__invoke')
|
|
|
|
) {
|
|
|
|
$invokable_storage = $this->getStorage($atomic_type->value . '::__invoke');
|
|
|
|
|
|
|
|
return new Type\Union([new Type\Atomic\Fn(
|
|
|
|
'Closure',
|
|
|
|
$invokable_storage->params,
|
|
|
|
$invokable_storage->return_type
|
|
|
|
)]);
|
|
|
|
}
|
2018-05-20 18:47:18 +02:00
|
|
|
}
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
return CallMap::getReturnTypeFromCallMap($appearing_method_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
$storage = $this->getStorage($declaring_method_id);
|
|
|
|
|
|
|
|
if ($storage->return_type) {
|
|
|
|
$self_class = $appearing_fq_class_name;
|
|
|
|
|
|
|
|
return clone $storage->return_type;
|
|
|
|
}
|
|
|
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name);
|
|
|
|
|
|
|
|
if (!isset($class_storage->overridden_method_ids[$appearing_method_name])) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($class_storage->overridden_method_ids[$appearing_method_name] as $overridden_method_id) {
|
|
|
|
$overridden_storage = $this->getStorage($overridden_method_id);
|
|
|
|
|
|
|
|
if ($overridden_storage->return_type) {
|
|
|
|
if ($overridden_storage->return_type->isNull()) {
|
|
|
|
return Type::getVoid();
|
|
|
|
}
|
|
|
|
|
|
|
|
list($fq_overridden_class) = explode('::', $overridden_method_id);
|
|
|
|
|
|
|
|
$overridden_class_storage =
|
|
|
|
$this->classlike_storage_provider->get($fq_overridden_class);
|
|
|
|
|
|
|
|
$overridden_return_type = clone $overridden_storage->return_type;
|
|
|
|
|
|
|
|
$self_class = $overridden_class_storage->name;
|
|
|
|
|
|
|
|
return $overridden_return_type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function getMethodReturnsByRef($method_id)
|
|
|
|
{
|
|
|
|
$method_id = $this->getDeclaringMethodId($method_id);
|
|
|
|
|
|
|
|
if (!$method_id) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
list($fq_class_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
$fq_class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
if (!$fq_class_storage->user_defined && CallMap::inCallMap($method_id)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$storage = $this->getStorage($method_id);
|
|
|
|
|
|
|
|
return $storage->returns_by_ref;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param CodeLocation|null $defined_location
|
|
|
|
*
|
|
|
|
* @return CodeLocation|null
|
|
|
|
*/
|
|
|
|
public function getMethodReturnTypeLocation(
|
|
|
|
$method_id,
|
|
|
|
CodeLocation &$defined_location = null
|
|
|
|
) {
|
|
|
|
$method_id = $this->getDeclaringMethodId($method_id);
|
|
|
|
|
|
|
|
if ($method_id === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$storage = $this->getStorage($method_id);
|
|
|
|
|
|
|
|
if (!$storage->return_type_location) {
|
|
|
|
$overridden_method_ids = $this->getOverriddenMethodIds($method_id);
|
|
|
|
|
|
|
|
foreach ($overridden_method_ids as $overridden_method_id) {
|
|
|
|
$overridden_storage = $this->getStorage($overridden_method_id);
|
|
|
|
|
|
|
|
if ($overridden_storage->return_type_location) {
|
|
|
|
$defined_location = $overridden_storage->return_type_location;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $storage->return_type_location;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string $declaring_method_id
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function setDeclaringMethodId(
|
|
|
|
$method_id,
|
|
|
|
$declaring_method_id
|
|
|
|
) {
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
$class_storage->declaring_method_ids[$method_name] = $declaring_method_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string $appearing_method_id
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function setAppearingMethodId(
|
|
|
|
$method_id,
|
|
|
|
$appearing_method_id
|
|
|
|
) {
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
$class_storage->appearing_method_ids[$method_name] = $appearing_method_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
*
|
|
|
|
* @return string|null
|
|
|
|
*/
|
|
|
|
public function getDeclaringMethodId($method_id)
|
|
|
|
{
|
|
|
|
$method_id = strtolower($method_id);
|
|
|
|
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
2018-12-21 17:32:44 +01:00
|
|
|
$fq_class_name = $this->classlikes->getUnAliasedName($fq_class_name);
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
if (isset($class_storage->declaring_method_ids[$method_name])) {
|
|
|
|
return $class_storage->declaring_method_ids[$method_name];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$method_name])) {
|
|
|
|
return $class_storage->overridden_method_ids[$method_name][0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the class this method appears in (vs is declared in, which could give a trait)
|
|
|
|
*
|
|
|
|
* @param string $method_id
|
|
|
|
*
|
|
|
|
* @return string|null
|
|
|
|
*/
|
|
|
|
public function getAppearingMethodId($method_id)
|
|
|
|
{
|
|
|
|
$method_id = strtolower($method_id);
|
|
|
|
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
2018-12-21 17:32:44 +01:00
|
|
|
$fq_class_name = $this->classlikes->getUnAliasedName($fq_class_name);
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
if (isset($class_storage->appearing_method_ids[$method_name])) {
|
|
|
|
return $class_storage->appearing_method_ids[$method_name];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
*
|
|
|
|
* @return array<string>
|
|
|
|
*/
|
|
|
|
public function getOverriddenMethodIds($method_id)
|
|
|
|
{
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
if (isset($class_storage->overridden_method_ids[$method_name])) {
|
|
|
|
return $class_storage->overridden_method_ids[$method_name];
|
|
|
|
}
|
|
|
|
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $original_method_id
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getCasedMethodId($original_method_id)
|
|
|
|
{
|
|
|
|
$method_id = $this->getDeclaringMethodId($original_method_id);
|
|
|
|
|
|
|
|
if ($method_id === null) {
|
2019-03-01 14:57:10 +01:00
|
|
|
return $original_method_id;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$storage = $this->getStorage($method_id);
|
|
|
|
|
|
|
|
list($fq_class_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
return $fq_class_name . '::' . $storage->cased_name;
|
|
|
|
}
|
|
|
|
|
2018-07-11 17:22:07 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
*
|
2018-10-27 17:47:27 +02:00
|
|
|
* @return ?MethodStorage
|
2018-07-11 17:22:07 +02:00
|
|
|
*/
|
|
|
|
public function getUserMethodStorage($method_id)
|
|
|
|
{
|
|
|
|
$declaring_method_id = $this->getDeclaringMethodId($method_id);
|
|
|
|
|
|
|
|
if (!$declaring_method_id) {
|
|
|
|
throw new \UnexpectedValueException('$storage should not be null for ' . $method_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
$storage = $this->getStorage($declaring_method_id);
|
|
|
|
|
|
|
|
if (!$storage->location) {
|
2018-10-27 17:47:27 +02:00
|
|
|
return null;
|
2018-07-11 17:22:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $storage;
|
|
|
|
}
|
|
|
|
|
2019-01-11 14:54:10 +01:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
*
|
|
|
|
* @return ClassLikeStorage
|
|
|
|
*/
|
|
|
|
public function getClassLikeStorageForMethod($method_id)
|
|
|
|
{
|
2019-03-01 14:57:10 +01:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
if ($this->existence_provider->has($fq_class_name)) {
|
|
|
|
if ($this->existence_provider->doesMethodExist(
|
|
|
|
$fq_class_name,
|
|
|
|
$method_name,
|
|
|
|
null,
|
|
|
|
null
|
|
|
|
)) {
|
|
|
|
return $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-11 14:54:10 +01:00
|
|
|
$declaring_method_id = $this->getDeclaringMethodId($method_id);
|
|
|
|
|
|
|
|
if (!$declaring_method_id) {
|
|
|
|
if (CallMap::inCallMap($method_id)) {
|
|
|
|
$declaring_method_id = $method_id;
|
|
|
|
} else {
|
|
|
|
throw new \UnexpectedValueException('$storage should not be null for ' . $method_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
list($declaring_fq_class_name) = explode('::', $declaring_method_id);
|
|
|
|
|
|
|
|
return $this->classlike_storage_provider->get($declaring_fq_class_name);
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
*
|
|
|
|
* @return MethodStorage
|
|
|
|
*/
|
|
|
|
public function getStorage($method_id)
|
|
|
|
{
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
$method_name_lc = strtolower($method_name);
|
|
|
|
|
|
|
|
if (!isset($class_storage->methods[$method_name_lc])) {
|
|
|
|
throw new \UnexpectedValueException('$storage should not be null for ' . $method_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $class_storage->methods[$method_name_lc];
|
|
|
|
}
|
|
|
|
}
|