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
|
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
|
use function array_pop;
|
|
|
|
|
use function assert;
|
|
|
|
|
use function count;
|
|
|
|
|
use function explode;
|
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-29 18:26:13 +01:00
|
|
|
|
use Psalm\Context;
|
2020-07-22 01:40:35 +02:00
|
|
|
|
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
|
2020-06-07 05:27:25 +02:00
|
|
|
|
use Psalm\Internal\MethodIdentifier;
|
2019-07-05 22:24:00 +02:00
|
|
|
|
use Psalm\Internal\Provider\ClassLikeStorageProvider;
|
|
|
|
|
use Psalm\Internal\Provider\FileReferenceProvider;
|
|
|
|
|
use Psalm\Internal\Provider\MethodExistenceProvider;
|
|
|
|
|
use Psalm\Internal\Provider\MethodParamsProvider;
|
|
|
|
|
use Psalm\Internal\Provider\MethodReturnTypeProvider;
|
|
|
|
|
use Psalm\Internal\Provider\MethodVisibilityProvider;
|
2019-03-01 14:57:10 +01:00
|
|
|
|
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;
|
2019-06-26 22:52:29 +02:00
|
|
|
|
use function reset;
|
2019-07-05 22:24:00 +02:00
|
|
|
|
use function strtolower;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
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;
|
|
|
|
|
|
2019-04-13 00:28:07 +02:00
|
|
|
|
/**
|
|
|
|
|
* @var bool
|
|
|
|
|
*/
|
|
|
|
|
public $collect_locations = 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;
|
2020-10-05 05:32:01 +02:00
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
public function __construct(
|
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-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
|
2020-03-26 17:35:27 +01:00
|
|
|
|
* @param lowercase-string|null $calling_method_id
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*/
|
|
|
|
|
public function methodExists(
|
2020-06-07 05:27:25 +02:00
|
|
|
|
MethodIdentifier $method_id,
|
2020-03-26 17:35:27 +01:00
|
|
|
|
?string $calling_method_id = null,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?CodeLocation $code_location = null,
|
|
|
|
|
?StatementsSource $source = null,
|
|
|
|
|
?string $source_file_path = null,
|
2020-09-01 00:56:21 +02:00
|
|
|
|
bool $use_method_existence_provider = true
|
2020-03-26 17:35:27 +01:00
|
|
|
|
) : bool {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$fq_class_name = $method_id->fq_class_name;
|
|
|
|
|
$method_name = $method_id->method_name;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2020-09-01 00:56:21 +02:00
|
|
|
|
if ($use_method_existence_provider && $this->existence_provider->has($fq_class_name)) {
|
2019-03-01 14:57:10 +01:00
|
|
|
|
$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;
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$fq_class_name = strtolower($this->classlikes->getUnAliasedName($fq_class_name));
|
2018-12-21 17:32:44 +01:00
|
|
|
|
|
2019-04-17 21:45:40 +02:00
|
|
|
|
try {
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2020-07-03 18:59:07 +02:00
|
|
|
|
$source_file_path = $source ? $source->getFilePath() : $source_file_path;
|
|
|
|
|
|
|
|
|
|
$calling_class_name = $source ? $source->getFQCLN() : null;
|
|
|
|
|
|
|
|
|
|
if (!$calling_class_name && $calling_method_id) {
|
|
|
|
|
$calling_class_name = explode('::', $calling_method_id)[0];
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
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];
|
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_method_id === strtolower((string) $declaring_method_id)) {
|
2018-09-26 00:37:24 +02:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-06 19:48:59 +01:00
|
|
|
|
$declaring_fq_class_name = strtolower($declaring_method_id->fq_class_name);
|
|
|
|
|
|
2020-07-03 18:59:07 +02:00
|
|
|
|
if ($declaring_fq_class_name !== strtolower((string) $calling_class_name)) {
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_method_id) {
|
|
|
|
|
$this->file_reference_provider->addMethodReferenceToClass(
|
|
|
|
|
$calling_method_id,
|
|
|
|
|
$declaring_fq_class_name
|
|
|
|
|
);
|
2020-07-03 18:59:07 +02:00
|
|
|
|
} elseif ($source_file_path) {
|
2020-04-02 23:17:55 +02:00
|
|
|
|
$this->file_reference_provider->addNonMethodReferenceToClass(
|
2020-07-03 18:59:07 +02:00
|
|
|
|
$source_file_path,
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$declaring_fq_class_name
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-03-06 19:48:59 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
if ((string) $method_id !== (string) $declaring_method_id
|
2019-04-16 22:07:48 +02:00
|
|
|
|
&& $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 => $_) {
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_method_id) {
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$this->file_reference_provider->addMethodReferenceToClassMember(
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$calling_method_id,
|
2018-09-26 00:37:24 +02:00
|
|
|
|
$potential_id
|
|
|
|
|
);
|
2020-03-06 19:02:34 +01:00
|
|
|
|
} elseif ($source_file_path) {
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$this->file_reference_provider->addFileReferenceToClassMember(
|
2020-03-06 19:02:34 +01:00
|
|
|
|
$source_file_path,
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$potential_id
|
|
|
|
|
);
|
2018-09-26 00:37:24 +02:00
|
|
|
|
}
|
2019-04-16 22:07:48 +02:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_method_id) {
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$this->file_reference_provider->addMethodReferenceToClassMember(
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$calling_method_id,
|
2020-02-15 02:54:26 +01:00
|
|
|
|
strtolower((string) $declaring_method_id)
|
2018-09-26 00:37:24 +02:00
|
|
|
|
);
|
2020-03-06 19:02:34 +01:00
|
|
|
|
} elseif ($source_file_path) {
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$this->file_reference_provider->addFileReferenceToClassMember(
|
2020-03-06 19:02:34 +01:00
|
|
|
|
$source_file_path,
|
2020-02-15 02:54:26 +01:00
|
|
|
|
strtolower((string) $declaring_method_id)
|
2019-04-16 22:07:48 +02:00
|
|
|
|
);
|
2018-09-26 00:37:24 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-16 22:07:48 +02:00
|
|
|
|
if ($this->collect_locations && $code_location) {
|
|
|
|
|
$this->file_reference_provider->addCallingLocationForClassMethod(
|
|
|
|
|
$code_location,
|
2020-02-15 02:54:26 +01:00
|
|
|
|
strtolower((string) $declaring_method_id)
|
2019-04-13 00:28:07 +02:00
|
|
|
|
);
|
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2019-04-16 22:07:48 +02:00
|
|
|
|
foreach ($class_storage->class_implements as $fq_interface_name) {
|
|
|
|
|
$interface_method_id_lc = strtolower($fq_interface_name . '::' . $method_name);
|
|
|
|
|
|
2019-04-13 00:28:07 +02:00
|
|
|
|
if ($this->collect_locations && $code_location) {
|
|
|
|
|
$this->file_reference_provider->addCallingLocationForClassMethod(
|
|
|
|
|
$code_location,
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$interface_method_id_lc
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_method_id) {
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$this->file_reference_provider->addMethodReferenceToClassMember(
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$calling_method_id,
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$interface_method_id_lc
|
|
|
|
|
);
|
2020-03-06 19:02:34 +01:00
|
|
|
|
} elseif ($source_file_path) {
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$this->file_reference_provider->addFileReferenceToClassMember(
|
2020-03-06 19:02:34 +01:00
|
|
|
|
$source_file_path,
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$interface_method_id_lc
|
2019-04-13 00:28:07 +02:00
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
2019-04-16 22:07:48 +02:00
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$declaring_method_class = $declaring_method_id->fq_class_name;
|
|
|
|
|
$declaring_method_name = $declaring_method_id->method_name;
|
2019-04-13 00:28:07 +02:00
|
|
|
|
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$declaring_class_storage = $this->classlike_storage_provider->get($declaring_method_class);
|
|
|
|
|
|
|
|
|
|
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) {
|
2019-04-13 00:28:07 +02:00
|
|
|
|
if ($this->collect_locations && $code_location) {
|
|
|
|
|
$this->file_reference_provider->addCallingLocationForClassMethod(
|
|
|
|
|
$code_location,
|
2020-02-15 02:54:26 +01:00
|
|
|
|
strtolower((string) $overridden_method_id)
|
2019-04-13 00:28:07 +02:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_method_id) {
|
2019-04-16 22:07:48 +02:00
|
|
|
|
// also store failures in case the method is added later
|
|
|
|
|
$this->file_reference_provider->addMethodReferenceToClassMember(
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$calling_method_id,
|
2020-02-15 02:54:26 +01:00
|
|
|
|
strtolower((string) $overridden_method_id)
|
2019-04-16 22:07:48 +02:00
|
|
|
|
);
|
2020-03-06 19:02:34 +01:00
|
|
|
|
} elseif ($source_file_path) {
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$this->file_reference_provider->addFileReferenceToClassMember(
|
2020-03-06 19:02:34 +01:00
|
|
|
|
$source_file_path,
|
2020-02-15 02:54:26 +01:00
|
|
|
|
strtolower((string) $overridden_method_id)
|
2019-04-13 00:28:07 +02:00
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-03 18:59:07 +02:00
|
|
|
|
if ($source_file_path && $fq_class_name !== strtolower((string) $calling_class_name)) {
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_method_id) {
|
|
|
|
|
$this->file_reference_provider->addMethodReferenceToClass(
|
|
|
|
|
$calling_method_id,
|
|
|
|
|
$fq_class_name
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2020-04-02 23:17:55 +02:00
|
|
|
|
$this->file_reference_provider->addNonMethodReferenceToClass(
|
2020-07-03 18:59:07 +02:00
|
|
|
|
$source_file_path,
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$fq_class_name
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-03-06 19:48:59 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
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
|
2020-05-25 19:10:06 +02:00
|
|
|
|
&& (InternalCallMapHandler::inCallMap((string) $method_id)
|
|
|
|
|
|| ($old_method_id && InternalCallMapHandler::inCallMap($old_method_id)))
|
2018-02-22 19:42:34 +01:00
|
|
|
|
) {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-16 22:07:48 +02:00
|
|
|
|
foreach ($class_storage->parent_classes + $class_storage->used_traits as $potential_future_declaring_fqcln) {
|
|
|
|
|
$potential_id = strtolower($potential_future_declaring_fqcln) . '::' . $method_name;
|
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_method_id) {
|
2019-04-16 22:07:48 +02:00
|
|
|
|
// also store failures in case the method is added later
|
|
|
|
|
$this->file_reference_provider->addMethodReferenceToMissingClassMember(
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$calling_method_id,
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$potential_id
|
|
|
|
|
);
|
2020-03-06 19:02:34 +01:00
|
|
|
|
} elseif ($source_file_path) {
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$this->file_reference_provider->addFileReferenceToMissingClassMember(
|
2020-03-06 19:02:34 +01:00
|
|
|
|
$source_file_path,
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$potential_id
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_method_id) {
|
2018-09-26 00:37:24 +02:00
|
|
|
|
// also store failures in case the method is added later
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$this->file_reference_provider->addMethodReferenceToMissingClassMember(
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$calling_method_id,
|
2020-02-15 02:54:26 +01:00
|
|
|
|
strtolower((string) $method_id)
|
2018-09-26 00:37:24 +02:00
|
|
|
|
);
|
2020-03-06 19:02:34 +01:00
|
|
|
|
} elseif ($source_file_path) {
|
2019-04-16 22:07:48 +02:00
|
|
|
|
$this->file_reference_provider->addFileReferenceToMissingClassMember(
|
2020-03-06 19:02:34 +01:00
|
|
|
|
$source_file_path,
|
2020-02-15 02:54:26 +01:00
|
|
|
|
strtolower((string) $method_id)
|
2019-04-16 22:07:48 +02:00
|
|
|
|
);
|
2018-09-26 00:37:24 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-10-28 17:45:26 +01:00
|
|
|
|
* @param list<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-29 18:26:13 +01:00
|
|
|
|
public function getMethodParams(
|
2020-06-07 05:27:25 +02:00
|
|
|
|
MethodIdentifier $method_id,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?StatementsSource $source = null,
|
|
|
|
|
?array $args = null,
|
|
|
|
|
?Context $context = null
|
2019-03-29 18:26:13 +01:00
|
|
|
|
) : array {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$fq_class_name = $method_id->fq_class_name;
|
|
|
|
|
$method_name = $method_id->method_name;
|
2019-03-01 14:57:10 +01:00
|
|
|
|
|
|
|
|
|
if ($this->params_provider->has($fq_class_name)) {
|
|
|
|
|
$method_params = $this->params_provider->getMethodParams(
|
|
|
|
|
$fq_class_name,
|
|
|
|
|
$method_name,
|
|
|
|
|
$args,
|
2019-03-29 18:26:13 +01:00
|
|
|
|
$source,
|
|
|
|
|
$context
|
2019-03-01 14:57:10 +01:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($method_params !== null) {
|
|
|
|
|
return $method_params;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-29 18:26:13 +01:00
|
|
|
|
$declaring_method_id = $this->getDeclaringMethodId($method_id);
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$callmap_id = $declaring_method_id ?: $method_id;
|
2019-03-29 18:26:13 +01:00
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
// functions
|
2020-05-25 19:10:06 +02:00
|
|
|
|
if (InternalCallMapHandler::inCallMap((string) $callmap_id)) {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($callmap_id->fq_class_name);
|
2019-03-29 18:26:13 +01:00
|
|
|
|
|
2020-10-30 22:37:16 +01:00
|
|
|
|
$declaring_method_name = $declaring_method_id ? $declaring_method_id->method_name : $method_name;
|
|
|
|
|
|
|
|
|
|
if (!$class_storage->stubbed || empty($class_storage->methods[$declaring_method_name]->stubbed)) {
|
2020-05-25 19:10:06 +02:00
|
|
|
|
$function_callables = InternalCallMapHandler::getCallablesFromCallMap((string) $callmap_id);
|
2019-03-29 18:26:13 +01:00
|
|
|
|
|
2019-06-15 22:10:48 +02:00
|
|
|
|
if ($function_callables === null) {
|
2019-03-29 18:26:13 +01:00
|
|
|
|
throw new \UnexpectedValueException(
|
2020-02-15 02:54:26 +01:00
|
|
|
|
'Not expecting $function_callables to be null for ' . $callmap_id
|
2019-03-29 18:26:13 +01:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-15 22:10:48 +02:00
|
|
|
|
if (!$source || $args === null || count($function_callables) === 1) {
|
|
|
|
|
assert($function_callables[0]->params !== null);
|
|
|
|
|
|
|
|
|
|
return $function_callables[0]->params;
|
2019-03-29 18:26:13 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($context && $source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) {
|
2019-08-13 20:53:31 +02:00
|
|
|
|
$was_inside_call = $context->inside_call;
|
|
|
|
|
|
|
|
|
|
$context->inside_call = true;
|
|
|
|
|
|
2019-03-29 18:26:13 +01:00
|
|
|
|
foreach ($args as $arg) {
|
|
|
|
|
\Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(
|
|
|
|
|
$source,
|
|
|
|
|
$arg->value,
|
|
|
|
|
$context
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-08-13 20:53:31 +02:00
|
|
|
|
|
|
|
|
|
if (!$was_inside_call) {
|
|
|
|
|
$context->inside_call = false;
|
|
|
|
|
}
|
2019-03-29 18:26:13 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-25 19:10:06 +02:00
|
|
|
|
$matching_callable = InternalCallMapHandler::getMatchingCallableFromCallMapOptions(
|
2019-03-29 18:26:13 +01:00
|
|
|
|
$source->getCodebase(),
|
2019-06-15 22:10:48 +02:00
|
|
|
|
$function_callables,
|
2019-11-25 17:44:54 +01:00
|
|
|
|
$args,
|
2020-10-12 18:09:08 +02:00
|
|
|
|
$source->getNodeTypeProvider(),
|
|
|
|
|
(string) $callmap_id
|
2019-03-29 18:26:13 +01:00
|
|
|
|
);
|
2019-06-15 22:10:48 +02:00
|
|
|
|
|
|
|
|
|
assert($matching_callable->params !== null);
|
|
|
|
|
|
|
|
|
|
return $matching_callable->params;
|
2019-03-29 18:26:13 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($declaring_method_id) {
|
2019-01-24 21:03:13 +01:00
|
|
|
|
$storage = $this->getStorage($declaring_method_id);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2018-10-06 19:46:35 +02:00
|
|
|
|
$params = $storage->params;
|
|
|
|
|
|
2019-03-25 16:25:43 +01:00
|
|
|
|
if ($storage->has_docblock_param_types) {
|
2018-10-06 19:46:35 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$appearing_fq_class_name = $appearing_method_id->fq_class_name;
|
|
|
|
|
$appearing_method_name = $appearing_method_id->method_name;
|
2018-10-06 19:46:35 +02:00
|
|
|
|
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name);
|
|
|
|
|
|
|
|
|
|
if (!isset($class_storage->overridden_method_ids[$appearing_method_name])) {
|
|
|
|
|
return $params;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-25 16:25:43 +01:00
|
|
|
|
if (!isset($class_storage->documenting_method_ids[$appearing_method_name])) {
|
|
|
|
|
return $params;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$overridden_method_id = $class_storage->documenting_method_ids[$appearing_method_name];
|
2019-12-20 02:42:57 +01:00
|
|
|
|
|
2019-03-25 16:25:43 +01:00
|
|
|
|
$overridden_storage = $this->getStorage($overridden_method_id);
|
2018-10-06 19:46:35 +02:00
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$overriding_fq_class_name = $overridden_method_id->fq_class_name;
|
2019-03-14 15:11:45 +01:00
|
|
|
|
|
2019-03-25 16:25:43 +01:00
|
|
|
|
foreach ($params as $i => $param) {
|
|
|
|
|
if (isset($overridden_storage->params[$i]->type)
|
|
|
|
|
&& $overridden_storage->params[$i]->has_docblock_type
|
|
|
|
|
) {
|
|
|
|
|
$params[$i] = clone $param;
|
|
|
|
|
/** @var Type\Union $params[$i]->type */
|
|
|
|
|
$params[$i]->type = clone $overridden_storage->params[$i]->type;
|
|
|
|
|
|
|
|
|
|
if ($source) {
|
2019-06-25 05:31:06 +02:00
|
|
|
|
$overridden_class_storage = $this->classlike_storage_provider->get($overriding_fq_class_name);
|
2019-10-04 19:51:28 +02:00
|
|
|
|
$params[$i]->type = self::localizeType(
|
2019-03-25 16:25:43 +01:00
|
|
|
|
$source->getCodebase(),
|
|
|
|
|
$params[$i]->type,
|
|
|
|
|
$appearing_fq_class_name,
|
2019-06-25 05:31:06 +02:00
|
|
|
|
$overridden_class_storage->name
|
2019-03-25 16:25:43 +01:00
|
|
|
|
);
|
2018-10-06 19:46:35 +02:00
|
|
|
|
}
|
2019-03-25 16:25:43 +01:00
|
|
|
|
|
|
|
|
|
if ($params[$i]->signature_type
|
|
|
|
|
&& $params[$i]->signature_type->isNullable()
|
|
|
|
|
) {
|
|
|
|
|
$params[$i]->type->addType(new Type\Atomic\TNull);
|
2018-10-06 19:46:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-25 16:25:43 +01:00
|
|
|
|
$params[$i]->type_location = $overridden_storage->params[$i]->type_location;
|
2018-10-06 19:46:35 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $params;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new \UnexpectedValueException('Cannot get method params for ' . $method_id);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 19:51:28 +02:00
|
|
|
|
public static function localizeType(
|
2019-03-14 15:11:45 +01:00
|
|
|
|
Codebase $codebase,
|
|
|
|
|
Type\Union $type,
|
|
|
|
|
string $appearing_fq_class_name,
|
|
|
|
|
string $base_fq_class_name
|
2019-03-16 16:15:25 +01:00
|
|
|
|
) : Type\Union {
|
2019-03-14 15:11:45 +01:00
|
|
|
|
$class_storage = $codebase->classlike_storage_provider->get($appearing_fq_class_name);
|
|
|
|
|
$extends = $class_storage->template_type_extends;
|
|
|
|
|
|
|
|
|
|
if (!$extends) {
|
2019-03-16 16:15:25 +01:00
|
|
|
|
return $type;
|
2019-03-14 15:11:45 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-16 16:15:25 +01:00
|
|
|
|
$type = clone $type;
|
|
|
|
|
|
2020-01-04 18:20:26 +01:00
|
|
|
|
foreach ($type->getAtomicTypes() as $key => $atomic_type) {
|
2020-03-15 21:14:09 +01:00
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TTemplateParam
|
2020-06-13 22:37:39 +02:00
|
|
|
|
&& ($atomic_type->defining_class === $base_fq_class_name
|
|
|
|
|
|| isset($extends[$atomic_type->defining_class]))
|
2020-03-15 21:14:09 +01:00
|
|
|
|
) {
|
2020-03-15 20:49:13 +01:00
|
|
|
|
$types_to_add = self::getExtendedTemplatedTypes(
|
|
|
|
|
$atomic_type,
|
|
|
|
|
$extends
|
|
|
|
|
);
|
2019-03-14 15:11:45 +01:00
|
|
|
|
|
2020-03-15 20:49:13 +01:00
|
|
|
|
if ($types_to_add) {
|
|
|
|
|
$type->removeType($key);
|
|
|
|
|
|
|
|
|
|
foreach ($types_to_add as $extra_added_type) {
|
|
|
|
|
$type->addType($extra_added_type);
|
2019-03-14 15:11:45 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-15 19:23:04 +01:00
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TTemplateParamClass) {
|
|
|
|
|
if ($atomic_type->defining_class === $base_fq_class_name) {
|
|
|
|
|
if (isset($extends[$base_fq_class_name][$atomic_type->param_name])) {
|
|
|
|
|
$extended_param = $extends[$base_fq_class_name][$atomic_type->param_name];
|
|
|
|
|
|
2020-01-04 18:20:26 +01:00
|
|
|
|
$types = \array_values($extended_param->getAtomicTypes());
|
2019-12-15 19:28:38 +01:00
|
|
|
|
|
|
|
|
|
if (count($types) === 1 && $types[0] instanceof Type\Atomic\TNamedObject) {
|
|
|
|
|
$atomic_type->as_type = $types[0];
|
|
|
|
|
} else {
|
|
|
|
|
$atomic_type->as_type = null;
|
|
|
|
|
}
|
2019-12-15 19:23:04 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-14 15:11:45 +01:00
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TArray
|
|
|
|
|
|| $atomic_type instanceof Type\Atomic\TIterable
|
|
|
|
|
|| $atomic_type instanceof Type\Atomic\TGenericObject
|
|
|
|
|
) {
|
2019-05-13 05:13:27 +02:00
|
|
|
|
foreach ($atomic_type->type_params as &$type_param) {
|
2019-10-04 19:51:28 +02:00
|
|
|
|
$type_param = self::localizeType(
|
2019-03-14 15:11:45 +01:00
|
|
|
|
$codebase,
|
|
|
|
|
$type_param,
|
|
|
|
|
$appearing_fq_class_name,
|
|
|
|
|
$base_fq_class_name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-18 21:23:09 +01:00
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TList) {
|
|
|
|
|
$atomic_type->type_param = self::localizeType(
|
|
|
|
|
$codebase,
|
|
|
|
|
$atomic_type->type_param,
|
|
|
|
|
$appearing_fq_class_name,
|
|
|
|
|
$base_fq_class_name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-30 17:44:14 +02:00
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TKeyedArray) {
|
2020-04-20 00:46:24 +02:00
|
|
|
|
foreach ($atomic_type->properties as &$property_type) {
|
|
|
|
|
$property_type = self::localizeType(
|
|
|
|
|
$codebase,
|
|
|
|
|
$property_type,
|
|
|
|
|
$appearing_fq_class_name,
|
|
|
|
|
$base_fq_class_name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-14 15:11:45 +01:00
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TCallable
|
2020-10-05 05:32:01 +02:00
|
|
|
|
|| $atomic_type instanceof Type\Atomic\TClosure
|
2019-03-14 15:11:45 +01:00
|
|
|
|
) {
|
|
|
|
|
if ($atomic_type->params) {
|
|
|
|
|
foreach ($atomic_type->params as $param) {
|
|
|
|
|
if ($param->type) {
|
2019-10-04 19:51:28 +02:00
|
|
|
|
$param->type = self::localizeType(
|
2019-03-14 15:11:45 +01:00
|
|
|
|
$codebase,
|
|
|
|
|
$param->type,
|
|
|
|
|
$appearing_fq_class_name,
|
|
|
|
|
$base_fq_class_name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($atomic_type->return_type) {
|
2019-10-04 19:51:28 +02:00
|
|
|
|
$atomic_type->return_type = self::localizeType(
|
2019-03-14 15:11:45 +01:00
|
|
|
|
$codebase,
|
|
|
|
|
$atomic_type->return_type,
|
|
|
|
|
$appearing_fq_class_name,
|
|
|
|
|
$base_fq_class_name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-16 16:15:25 +01:00
|
|
|
|
|
2020-04-20 00:46:24 +02:00
|
|
|
|
$type->bustCache();
|
|
|
|
|
|
2019-03-16 16:15:25 +01:00
|
|
|
|
return $type;
|
2019-03-14 15:11:45 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-15 20:49:13 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param array<string, array<int|string, Type\Union>> $extends
|
|
|
|
|
* @return list<Type\Atomic>
|
|
|
|
|
*/
|
2020-04-19 18:19:39 +02:00
|
|
|
|
public static function getExtendedTemplatedTypes(
|
2020-03-15 20:49:13 +01:00
|
|
|
|
Type\Atomic\TTemplateParam $atomic_type,
|
|
|
|
|
array $extends
|
|
|
|
|
) : array {
|
|
|
|
|
$extra_added_types = [];
|
|
|
|
|
|
|
|
|
|
if (isset($extends[$atomic_type->defining_class][$atomic_type->param_name])) {
|
|
|
|
|
$extended_param = clone $extends[$atomic_type->defining_class][$atomic_type->param_name];
|
|
|
|
|
|
|
|
|
|
foreach ($extended_param->getAtomicTypes() as $extended_atomic_type) {
|
|
|
|
|
if ($extended_atomic_type instanceof Type\Atomic\TTemplateParam) {
|
|
|
|
|
$extra_added_types = \array_merge(
|
|
|
|
|
$extra_added_types,
|
|
|
|
|
self::getExtendedTemplatedTypes(
|
|
|
|
|
$extended_atomic_type,
|
|
|
|
|
$extends
|
|
|
|
|
)
|
|
|
|
|
);
|
2020-03-15 21:14:09 +01:00
|
|
|
|
} else {
|
|
|
|
|
$extra_added_types[] = $extended_atomic_type;
|
2020-03-15 20:49:13 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-15 21:14:09 +01:00
|
|
|
|
} else {
|
|
|
|
|
$extra_added_types[] = $atomic_type;
|
2020-03-15 20:49:13 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $extra_added_types;
|
|
|
|
|
}
|
2020-10-05 05:32:01 +02:00
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
|
public function isVariadic(MethodIdentifier $method_id): bool
|
2018-02-04 00:52:35 +01:00
|
|
|
|
{
|
2019-03-01 14:57:10 +01:00
|
|
|
|
$declaring_method_id = $this->getDeclaringMethodId($method_id);
|
|
|
|
|
|
|
|
|
|
if (!$declaring_method_id) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
return $this->getStorage($declaring_method_id)->variadic;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-10-28 17:45:26 +01:00
|
|
|
|
* @param list<PhpParser\Node\Arg>|null $args
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
2019-11-25 17:44:54 +01:00
|
|
|
|
public function getMethodReturnType(
|
2020-06-07 05:27:25 +02:00
|
|
|
|
MethodIdentifier $method_id,
|
2020-09-01 15:19:50 +02:00
|
|
|
|
?string &$self_class,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?\Psalm\Internal\Analyzer\SourceAnalyzer $source_analyzer = null,
|
|
|
|
|
?array $args = null
|
2020-09-04 22:26:33 +02:00
|
|
|
|
): ?Type\Union {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$original_fq_class_name = $method_id->fq_class_name;
|
|
|
|
|
$original_method_name = $method_id->method_name;
|
|
|
|
|
|
|
|
|
|
$adjusted_fq_class_name = $this->classlikes->getUnAliasedName($original_fq_class_name);
|
2018-11-30 21:13:25 +01:00
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
if ($adjusted_fq_class_name !== $original_fq_class_name) {
|
|
|
|
|
$original_fq_class_name = strtolower($adjusted_fq_class_name);
|
|
|
|
|
}
|
2018-04-22 05:08:08 +02:00
|
|
|
|
|
2019-05-27 05:35:03 +02:00
|
|
|
|
$original_class_storage = $this->classlike_storage_provider->get($original_fq_class_name);
|
2018-12-21 17:32:44 +01:00
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
if (isset($original_class_storage->pseudo_methods[$original_method_name])) {
|
|
|
|
|
return $original_class_storage->pseudo_methods[$original_method_name]->return_type;
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$appearing_method_id = $this->getAppearingMethodId($method_id);
|
|
|
|
|
|
|
|
|
|
if (!$appearing_method_id) {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($original_fq_class_name);
|
2018-11-11 01:05:51 +01:00
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$original_method_name])) {
|
|
|
|
|
$appearing_method_id = reset($class_storage->overridden_method_ids[$original_method_name]);
|
2018-11-11 01:05:51 +01:00
|
|
|
|
} else {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$appearing_fq_class_name = $appearing_method_id->fq_class_name;
|
|
|
|
|
$appearing_method_name = $appearing_method_id->method_name;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
|
|
$appearing_fq_class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name);
|
|
|
|
|
|
2019-06-24 23:45:10 +02:00
|
|
|
|
if (!$appearing_fq_class_storage->user_defined
|
|
|
|
|
&& !$appearing_fq_class_storage->stubbed
|
2020-05-25 19:10:06 +02:00
|
|
|
|
&& InternalCallMapHandler::inCallMap((string) $appearing_method_id)
|
2019-06-24 23:45:10 +02:00
|
|
|
|
) {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
if ((string) $appearing_method_id === 'Closure::fromcallable'
|
2019-11-25 17:44:54 +01:00
|
|
|
|
&& isset($args[0])
|
2019-12-20 02:42:57 +01:00
|
|
|
|
&& $source_analyzer
|
|
|
|
|
&& ($first_arg_type = $source_analyzer->getNodeTypeProvider()->getType($args[0]->value))
|
2019-11-25 17:44:54 +01:00
|
|
|
|
&& $first_arg_type->isSingle()
|
2018-05-20 18:47:18 +02:00
|
|
|
|
) {
|
2020-01-04 18:20:26 +01:00
|
|
|
|
foreach ($first_arg_type->getAtomicTypes() as $atomic_type) {
|
2019-01-21 23:08:12 +01:00
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TCallable
|
2020-10-05 05:32:01 +02:00
|
|
|
|
|| $atomic_type instanceof Type\Atomic\TClosure
|
2019-01-21 23:08:12 +01:00
|
|
|
|
) {
|
2018-05-20 18:47:18 +02:00
|
|
|
|
$callable_type = clone $atomic_type;
|
|
|
|
|
|
2020-10-05 05:32:01 +02:00
|
|
|
|
return new Type\Union([new Type\Atomic\TClosure(
|
2018-05-20 18:47:18 +02:00
|
|
|
|
'Closure',
|
|
|
|
|
$callable_type->params,
|
|
|
|
|
$callable_type->return_type
|
|
|
|
|
)]);
|
|
|
|
|
}
|
2019-01-21 23:08:12 +01:00
|
|
|
|
|
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TNamedObject
|
2020-02-15 02:54:26 +01:00
|
|
|
|
&& $this->methodExists(
|
2020-06-07 05:27:25 +02:00
|
|
|
|
new MethodIdentifier($atomic_type->value, '__invoke')
|
2020-02-15 02:54:26 +01:00
|
|
|
|
)
|
2019-01-21 23:08:12 +01:00
|
|
|
|
) {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$invokable_storage = $this->getStorage(
|
2020-06-07 05:27:25 +02:00
|
|
|
|
new MethodIdentifier($atomic_type->value, '__invoke')
|
2020-02-15 02:54:26 +01:00
|
|
|
|
);
|
2019-01-21 23:08:12 +01:00
|
|
|
|
|
2020-10-05 05:32:01 +02:00
|
|
|
|
return new Type\Union([new Type\Atomic\TClosure(
|
2019-01-21 23:08:12 +01:00
|
|
|
|
'Closure',
|
|
|
|
|
$invokable_storage->params,
|
|
|
|
|
$invokable_storage->return_type
|
|
|
|
|
)]);
|
|
|
|
|
}
|
2018-05-20 18:47:18 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-21 04:11:20 +02:00
|
|
|
|
|
2020-05-25 19:10:06 +02:00
|
|
|
|
$callmap_callables = InternalCallMapHandler::getCallablesFromCallMap((string) $appearing_method_id);
|
2019-06-15 22:10:48 +02:00
|
|
|
|
|
|
|
|
|
if (!$callmap_callables || $callmap_callables[0]->return_type === null) {
|
|
|
|
|
throw new \UnexpectedValueException('Shouldn’t get here');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$return_type_candidate = $callmap_callables[0]->return_type;
|
2019-04-21 04:11:20 +02:00
|
|
|
|
|
|
|
|
|
if ($return_type_candidate->isFalsable()) {
|
|
|
|
|
$return_type_candidate->ignore_falsable_issues = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $return_type_candidate;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$storage = $this->getStorage($declaring_method_id);
|
|
|
|
|
|
|
|
|
|
if ($storage->return_type) {
|
2020-01-02 21:23:57 +01:00
|
|
|
|
$self_class = $appearing_fq_class_storage->name;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-20 02:42:57 +01:00
|
|
|
|
$candidate_type = null;
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
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()) {
|
2019-12-20 02:42:57 +01:00
|
|
|
|
if ($candidate_type && !$candidate_type->isVoid()) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$candidate_type = Type::getVoid();
|
|
|
|
|
continue;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$fq_overridden_class = $overridden_method_id->fq_class_name;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
|
|
$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;
|
|
|
|
|
|
2020-11-12 01:22:23 +01:00
|
|
|
|
if ($candidate_type && $source_analyzer && !$candidate_type->isMixed()) {
|
2020-07-22 01:40:35 +02:00
|
|
|
|
$old_contained_by_new = UnionTypeComparator::isContainedBy(
|
2019-12-20 02:42:57 +01:00
|
|
|
|
$source_analyzer->getCodebase(),
|
|
|
|
|
$candidate_type,
|
|
|
|
|
$overridden_return_type
|
|
|
|
|
);
|
|
|
|
|
|
2020-07-22 01:40:35 +02:00
|
|
|
|
$new_contained_by_old = UnionTypeComparator::isContainedBy(
|
2019-12-20 02:42:57 +01:00
|
|
|
|
$source_analyzer->getCodebase(),
|
|
|
|
|
$overridden_return_type,
|
|
|
|
|
$candidate_type
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!$old_contained_by_new && !$new_contained_by_old) {
|
|
|
|
|
$attempted_intersection = Type::intersectUnionTypes(
|
|
|
|
|
$candidate_type,
|
2020-04-07 06:13:56 +02:00
|
|
|
|
$overridden_return_type,
|
|
|
|
|
$source_analyzer->getCodebase()
|
2019-12-20 02:42:57 +01:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($attempted_intersection) {
|
|
|
|
|
$candidate_type = $attempted_intersection;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($old_contained_by_new) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$candidate_type = $overridden_return_type;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-20 02:42:57 +01:00
|
|
|
|
return $candidate_type;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
|
public function getMethodReturnsByRef(MethodIdentifier $method_id): bool
|
2018-02-04 00:52:35 +01:00
|
|
|
|
{
|
|
|
|
|
$method_id = $this->getDeclaringMethodId($method_id);
|
|
|
|
|
|
|
|
|
|
if (!$method_id) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$fq_class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2020-05-25 19:10:06 +02:00
|
|
|
|
if (!$fq_class_storage->user_defined && InternalCallMapHandler::inCallMap((string) $method_id)) {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$storage = $this->getStorage($method_id);
|
|
|
|
|
|
|
|
|
|
return $storage->returns_by_ref;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param CodeLocation|null $defined_location
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
public function getMethodReturnTypeLocation(
|
2020-06-07 05:27:25 +02:00
|
|
|
|
MethodIdentifier $method_id,
|
2018-02-04 00:52:35 +01:00
|
|
|
|
CodeLocation &$defined_location = null
|
2020-09-04 22:26:33 +02:00
|
|
|
|
): ?CodeLocation {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* @param lowercase-string $method_name_lc
|
|
|
|
|
* @param lowercase-string $declaring_method_name_lc
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
public function setDeclaringMethodId(
|
2020-09-07 01:36:47 +02:00
|
|
|
|
string $fq_class_name,
|
|
|
|
|
string $method_name_lc,
|
|
|
|
|
string $declaring_fq_class_name,
|
|
|
|
|
string $declaring_method_name_lc
|
2020-09-12 17:24:05 +02:00
|
|
|
|
): void {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
2020-06-07 05:27:25 +02:00
|
|
|
|
$class_storage->declaring_method_ids[$method_name_lc] = new MethodIdentifier(
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$declaring_fq_class_name,
|
|
|
|
|
$declaring_method_name_lc
|
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* @param lowercase-string $method_name_lc
|
|
|
|
|
* @param lowercase-string $appearing_method_name_lc
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
public function setAppearingMethodId(
|
2020-09-07 01:36:47 +02:00
|
|
|
|
string $fq_class_name,
|
|
|
|
|
string $method_name_lc,
|
|
|
|
|
string $appearing_fq_class_name,
|
|
|
|
|
string $appearing_method_name_lc
|
2020-09-12 17:24:05 +02:00
|
|
|
|
): void {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
2020-06-07 05:27:25 +02:00
|
|
|
|
$class_storage->appearing_method_ids[$method_name_lc] = new MethodIdentifier(
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$appearing_fq_class_name,
|
|
|
|
|
$appearing_method_name_lc
|
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
public function getDeclaringMethodId(
|
2020-06-07 05:27:25 +02:00
|
|
|
|
MethodIdentifier $method_id
|
|
|
|
|
) : ?MethodIdentifier {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$fq_class_name = $this->classlikes->getUnAliasedName($method_id->fq_class_name);
|
2018-12-21 17:32:44 +01:00
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$method_name = $method_id->method_name;
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
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])) {
|
2019-06-11 17:19:57 +02:00
|
|
|
|
return reset($class_storage->overridden_method_ids[$method_name]);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
2020-02-15 02:54:26 +01:00
|
|
|
|
|
|
|
|
|
return null;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* Get the class this method appears in (vs is declared in, which could give a trait
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*/
|
2020-02-15 02:54:26 +01:00
|
|
|
|
public function getAppearingMethodId(
|
2020-06-07 05:27:25 +02:00
|
|
|
|
MethodIdentifier $method_id
|
|
|
|
|
) : ?MethodIdentifier {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$fq_class_name = $this->classlikes->getUnAliasedName($method_id->fq_class_name);
|
2018-12-21 17:32:44 +01:00
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$method_name = $method_id->method_name;
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
if (isset($class_storage->appearing_method_ids[$method_name])) {
|
|
|
|
|
return $class_storage->appearing_method_ids[$method_name];
|
|
|
|
|
}
|
2020-02-15 02:54:26 +01:00
|
|
|
|
|
|
|
|
|
return null;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-10-17 18:36:44 +02:00
|
|
|
|
* @return array<string, MethodIdentifier>
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
|
public function getOverriddenMethodIds(MethodIdentifier $method_id): array
|
2018-02-04 00:52:35 +01:00
|
|
|
|
{
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name);
|
|
|
|
|
$method_name = $method_id->method_name;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
|
|
if (isset($class_storage->overridden_method_ids[$method_name])) {
|
|
|
|
|
return $class_storage->overridden_method_ids[$method_name];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-12 21:02:52 +02:00
|
|
|
|
public function getCasedMethodId(MethodIdentifier $original_method_id): string
|
2018-02-04 00:52:35 +01:00
|
|
|
|
{
|
|
|
|
|
$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
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$fq_class_name = $method_id->fq_class_name;
|
|
|
|
|
$new_method_name = $method_id->method_name;
|
|
|
|
|
|
|
|
|
|
$old_fq_class_name = $original_method_id->fq_class_name;
|
|
|
|
|
$old_method_name = $original_method_id->method_name;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$storage = $this->getStorage($method_id);
|
2019-10-19 23:59:10 +02:00
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
if ($old_method_name === $new_method_name
|
2019-10-19 23:59:10 +02:00
|
|
|
|
&& strtolower($old_fq_class_name) !== $old_fq_class_name
|
|
|
|
|
) {
|
|
|
|
|
return $old_fq_class_name . '::' . $storage->cased_name;
|
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
|
|
return $fq_class_name . '::' . $storage->cased_name;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
|
public function getUserMethodStorage(MethodIdentifier $method_id): ?MethodStorage
|
2018-07-11 17:22:07 +02:00
|
|
|
|
{
|
|
|
|
|
$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;
|
|
|
|
|
}
|
2020-10-05 05:32:01 +02:00
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
|
public function getClassLikeStorageForMethod(MethodIdentifier $method_id): ClassLikeStorage
|
2019-01-11 14:54:10 +01:00
|
|
|
|
{
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$fq_class_name = $method_id->fq_class_name;
|
|
|
|
|
$method_name = $method_id->method_name;
|
2019-03-01 14:57:10 +01:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
if ($declaring_method_id === null) {
|
2020-05-25 19:10:06 +02:00
|
|
|
|
if (InternalCallMapHandler::inCallMap((string) $method_id)) {
|
2019-01-11 14:54:10 +01:00
|
|
|
|
$declaring_method_id = $method_id;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$storage should not be null for ' . $method_id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$declaring_fq_class_name = $declaring_method_id->fq_class_name;
|
2019-01-11 14:54:10 +01:00
|
|
|
|
|
|
|
|
|
return $this->classlike_storage_provider->get($declaring_fq_class_name);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
|
public function getStorage(MethodIdentifier $method_id): MethodStorage
|
2018-02-04 00:52:35 +01:00
|
|
|
|
{
|
2019-11-21 14:56:47 +01:00
|
|
|
|
try {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name);
|
2019-11-21 14:56:47 +01:00
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
|
throw new \UnexpectedValueException($e->getMessage());
|
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$method_name = $method_id->method_name;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
if (!isset($class_storage->methods[$method_name])) {
|
|
|
|
|
throw new \UnexpectedValueException(
|
|
|
|
|
'$storage should not be null for ' . $method_id
|
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
return $class_storage->methods[$method_name];
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
2020-07-02 00:10:24 +02:00
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
|
public function hasStorage(MethodIdentifier $method_id): bool
|
2020-07-02 00:10:24 +02:00
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name);
|
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$method_name = $method_id->method_name;
|
|
|
|
|
|
|
|
|
|
if (!isset($class_storage->methods[$method_name])) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|