1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-15 02:47:02 +01:00
psalm/src/Psalm/Internal/Codebase/Methods.php
Matthew Brown cc7ff94f7c Prevent crash when method being called does not exist in reflection
Crash seen when running this test in PHP 7.4 because the method does not exist, but the call map includes it in 8.0
2021-05-13 12:40:39 -04:00

1187 lines
42 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Psalm\Internal\Codebase;
use function array_pop;
use function assert;
use function count;
use function explode;
use PhpParser;
use Psalm\Codebase;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Internal\MethodIdentifier;
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;
use Psalm\StatementsSource;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Storage\FunctionLikeParameter;
use Psalm\Storage\MethodStorage;
use Psalm\Type;
use function reset;
use function strtolower;
/**
* @internal
*
* Handles information about class methods
*/
class Methods
{
/**
* @var ClassLikeStorageProvider
*/
private $classlike_storage_provider;
/**
* @var bool
*/
public $collect_locations = false;
/**
* @var FileReferenceProvider
*/
public $file_reference_provider;
/**
* @var ClassLikes
*/
private $classlikes;
/** @var MethodReturnTypeProvider */
public $return_type_provider;
/** @var MethodParamsProvider */
public $params_provider;
/** @var MethodExistenceProvider */
public $existence_provider;
/** @var MethodVisibilityProvider */
public $visibility_provider;
public function __construct(
ClassLikeStorageProvider $storage_provider,
FileReferenceProvider $file_reference_provider,
ClassLikes $classlikes
) {
$this->classlike_storage_provider = $storage_provider;
$this->file_reference_provider = $file_reference_provider;
$this->classlikes = $classlikes;
$this->return_type_provider = new MethodReturnTypeProvider();
$this->existence_provider = new MethodExistenceProvider();
$this->visibility_provider = new MethodVisibilityProvider();
$this->params_provider = new MethodParamsProvider();
}
/**
* Whether or not a given method exists
* @param lowercase-string|null $calling_method_id
*/
public function methodExists(
MethodIdentifier $method_id,
?string $calling_method_id = null,
?CodeLocation $code_location = null,
?StatementsSource $source = null,
?string $source_file_path = null,
bool $use_method_existence_provider = true
) : bool {
$fq_class_name = $method_id->fq_class_name;
$method_name = $method_id->method_name;
if ($use_method_existence_provider && $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;
}
}
$old_method_id = null;
$fq_class_name = strtolower($this->classlikes->getUnAliasedName($fq_class_name));
try {
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
} catch (\InvalidArgumentException $e) {
return false;
}
if ($class_storage->is_enum && $method_name === 'cases') {
return true;
}
$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];
}
if (isset($class_storage->declaring_method_ids[$method_name])) {
$declaring_method_id = $class_storage->declaring_method_ids[$method_name];
if ($calling_method_id === strtolower((string) $declaring_method_id)) {
return true;
}
$declaring_fq_class_name = strtolower($declaring_method_id->fq_class_name);
if ($declaring_fq_class_name !== strtolower((string) $calling_class_name)) {
if ($calling_method_id) {
$this->file_reference_provider->addMethodReferenceToClass(
$calling_method_id,
$declaring_fq_class_name
);
} elseif ($source_file_path) {
$this->file_reference_provider->addNonMethodReferenceToClass(
$source_file_path,
$declaring_fq_class_name
);
}
}
if ((string) $method_id !== (string) $declaring_method_id
&& $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 => $_) {
if ($calling_method_id) {
$this->file_reference_provider->addMethodReferenceToClassMember(
$calling_method_id,
$potential_id
);
} elseif ($source_file_path) {
$this->file_reference_provider->addFileReferenceToClassMember(
$source_file_path,
$potential_id
);
}
}
} else {
if ($calling_method_id) {
$this->file_reference_provider->addMethodReferenceToClassMember(
$calling_method_id,
strtolower((string) $declaring_method_id)
);
} elseif ($source_file_path) {
$this->file_reference_provider->addFileReferenceToClassMember(
$source_file_path,
strtolower((string) $declaring_method_id)
);
}
}
if ($this->collect_locations && $code_location) {
$this->file_reference_provider->addCallingLocationForClassMethod(
$code_location,
strtolower((string) $declaring_method_id)
);
}
foreach ($class_storage->class_implements as $fq_interface_name) {
$interface_method_id_lc = strtolower($fq_interface_name . '::' . $method_name);
if ($this->collect_locations && $code_location) {
$this->file_reference_provider->addCallingLocationForClassMethod(
$code_location,
$interface_method_id_lc
);
}
if ($calling_method_id) {
$this->file_reference_provider->addMethodReferenceToClassMember(
$calling_method_id,
$interface_method_id_lc
);
} elseif ($source_file_path) {
$this->file_reference_provider->addFileReferenceToClassMember(
$source_file_path,
$interface_method_id_lc
);
}
}
$declaring_method_class = $declaring_method_id->fq_class_name;
$declaring_method_name = $declaring_method_id->method_name;
$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) {
if ($this->collect_locations && $code_location) {
$this->file_reference_provider->addCallingLocationForClassMethod(
$code_location,
strtolower((string) $overridden_method_id)
);
}
if ($calling_method_id) {
// also store failures in case the method is added later
$this->file_reference_provider->addMethodReferenceToClassMember(
$calling_method_id,
strtolower((string) $overridden_method_id)
);
} elseif ($source_file_path) {
$this->file_reference_provider->addFileReferenceToClassMember(
$source_file_path,
strtolower((string) $overridden_method_id)
);
}
}
}
return true;
}
if ($source_file_path && $fq_class_name !== strtolower((string) $calling_class_name)) {
if ($calling_method_id) {
$this->file_reference_provider->addMethodReferenceToClass(
$calling_method_id,
$fq_class_name
);
} else {
$this->file_reference_provider->addNonMethodReferenceToClass(
$source_file_path,
$fq_class_name
);
}
}
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;
}
if (!$class_storage->user_defined
&& (InternalCallMapHandler::inCallMap((string) $method_id)
|| ($old_method_id && InternalCallMapHandler::inCallMap($old_method_id)))
) {
return true;
}
foreach ($class_storage->parent_classes + $class_storage->used_traits as $potential_future_declaring_fqcln) {
$potential_id = strtolower($potential_future_declaring_fqcln) . '::' . $method_name;
if ($calling_method_id) {
// also store failures in case the method is added later
$this->file_reference_provider->addMethodReferenceToMissingClassMember(
$calling_method_id,
$potential_id
);
} elseif ($source_file_path) {
$this->file_reference_provider->addFileReferenceToMissingClassMember(
$source_file_path,
$potential_id
);
}
}
if ($calling_method_id) {
// also store failures in case the method is added later
$this->file_reference_provider->addMethodReferenceToMissingClassMember(
$calling_method_id,
strtolower((string) $method_id)
);
} elseif ($source_file_path) {
$this->file_reference_provider->addFileReferenceToMissingClassMember(
$source_file_path,
strtolower((string) $method_id)
);
}
return false;
}
/**
* @param list<PhpParser\Node\Arg> $args
*
* @return array<int, FunctionLikeParameter>
*/
public function getMethodParams(
MethodIdentifier $method_id,
?StatementsSource $source = null,
?array $args = null,
?Context $context = null
) : array {
$fq_class_name = $method_id->fq_class_name;
$method_name = $method_id->method_name;
if ($this->params_provider->has($fq_class_name)) {
$method_params = $this->params_provider->getMethodParams(
$fq_class_name,
$method_name,
$args,
$source,
$context
);
if ($method_params !== null) {
return $method_params;
}
}
$declaring_method_id = $this->getDeclaringMethodId($method_id);
$callmap_id = $declaring_method_id ?: $method_id;
// functions
if (InternalCallMapHandler::inCallMap((string) $callmap_id)) {
$class_storage = $this->classlike_storage_provider->get($callmap_id->fq_class_name);
$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)) {
$function_callables = InternalCallMapHandler::getCallablesFromCallMap((string) $callmap_id);
if ($function_callables === null) {
throw new \UnexpectedValueException(
'Not expecting $function_callables to be null for ' . $callmap_id
);
}
if (!$source || $args === null || count($function_callables) === 1) {
assert($function_callables[0]->params !== null);
return $function_callables[0]->params;
}
if ($context && $source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) {
$was_inside_call = $context->inside_call;
$context->inside_call = true;
foreach ($args as $arg) {
\Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(
$source,
$arg->value,
$context
);
}
if (!$was_inside_call) {
$context->inside_call = false;
}
}
$matching_callable = InternalCallMapHandler::getMatchingCallableFromCallMapOptions(
$source->getCodebase(),
$function_callables,
$args,
$source->getNodeTypeProvider(),
(string) $callmap_id
);
assert($matching_callable->params !== null);
return $matching_callable->params;
}
}
if ($declaring_method_id) {
$storage = $this->getStorage($declaring_method_id);
$params = $storage->params;
if ($storage->has_docblock_param_types) {
return $params;
}
$appearing_method_id = $this->getAppearingMethodId($declaring_method_id);
if (!$appearing_method_id) {
return $params;
}
$appearing_fq_class_name = $appearing_method_id->fq_class_name;
$appearing_method_name = $appearing_method_id->method_name;
$class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name);
if (!isset($class_storage->overridden_method_ids[$appearing_method_name])) {
return $params;
}
if (!isset($class_storage->documenting_method_ids[$appearing_method_name])) {
return $params;
}
$overridden_method_id = $class_storage->documenting_method_ids[$appearing_method_name];
$overridden_storage = $this->getStorage($overridden_method_id);
$overriding_fq_class_name = $overridden_method_id->fq_class_name;
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) {
$overridden_class_storage = $this->classlike_storage_provider->get($overriding_fq_class_name);
$params[$i]->type = self::localizeType(
$source->getCodebase(),
$params[$i]->type,
$appearing_fq_class_name,
$overridden_class_storage->name
);
}
if ($params[$i]->signature_type
&& $params[$i]->signature_type->isNullable()
) {
$params[$i]->type->addType(new Type\Atomic\TNull);
}
$params[$i]->type_location = $overridden_storage->params[$i]->type_location;
}
}
return $params;
}
throw new \UnexpectedValueException('Cannot get method params for ' . $method_id);
}
public static function localizeType(
Codebase $codebase,
Type\Union $type,
string $appearing_fq_class_name,
string $base_fq_class_name
) : Type\Union {
$class_storage = $codebase->classlike_storage_provider->get($appearing_fq_class_name);
$extends = $class_storage->template_extended_params;
if (!$extends) {
return $type;
}
$type = clone $type;
foreach ($type->getAtomicTypes() as $key => $atomic_type) {
if ($atomic_type instanceof Type\Atomic\TTemplateParam
&& ($atomic_type->defining_class === $base_fq_class_name
|| isset($extends[$atomic_type->defining_class]))
) {
$types_to_add = self::getExtendedTemplatedTypes(
$atomic_type,
$extends
);
if ($types_to_add) {
$type->removeType($key);
foreach ($types_to_add as $extra_added_type) {
$type->addType($extra_added_type);
}
}
}
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];
$types = \array_values($extended_param->getAtomicTypes());
if (count($types) === 1 && $types[0] instanceof Type\Atomic\TNamedObject) {
$atomic_type->as_type = $types[0];
} else {
$atomic_type->as_type = null;
}
}
}
}
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) {
$type_param = self::localizeType(
$codebase,
$type_param,
$appearing_fq_class_name,
$base_fq_class_name
);
}
}
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
);
}
if ($atomic_type instanceof Type\Atomic\TKeyedArray) {
foreach ($atomic_type->properties as &$property_type) {
$property_type = self::localizeType(
$codebase,
$property_type,
$appearing_fq_class_name,
$base_fq_class_name
);
}
}
if ($atomic_type instanceof Type\Atomic\TCallable
|| $atomic_type instanceof Type\Atomic\TClosure
) {
if ($atomic_type->params) {
foreach ($atomic_type->params as $param) {
if ($param->type) {
$param->type = self::localizeType(
$codebase,
$param->type,
$appearing_fq_class_name,
$base_fq_class_name
);
}
}
}
if ($atomic_type->return_type) {
$atomic_type->return_type = self::localizeType(
$codebase,
$atomic_type->return_type,
$appearing_fq_class_name,
$base_fq_class_name
);
}
}
}
$type->bustCache();
return $type;
}
/**
* @param array<string, array<string, Type\Union>> $extends
* @return list<Type\Atomic>
*/
public static function getExtendedTemplatedTypes(
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
)
);
} else {
$extra_added_types[] = $extended_atomic_type;
}
}
} else {
$extra_added_types[] = $atomic_type;
}
return $extra_added_types;
}
public function isVariadic(MethodIdentifier $method_id): bool
{
$declaring_method_id = $this->getDeclaringMethodId($method_id);
if (!$declaring_method_id) {
return false;
}
return $this->getStorage($declaring_method_id)->variadic;
}
/**
* @param list<PhpParser\Node\Arg>|null $args
*
*/
public function getMethodReturnType(
MethodIdentifier $method_id,
?string &$self_class,
?\Psalm\Internal\Analyzer\SourceAnalyzer $source_analyzer = null,
?array $args = null
): ?Type\Union {
$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);
if ($adjusted_fq_class_name !== $original_fq_class_name) {
$original_fq_class_name = strtolower($adjusted_fq_class_name);
}
$original_class_storage = $this->classlike_storage_provider->get($original_fq_class_name);
if (isset($original_class_storage->pseudo_methods[$original_method_name])) {
return $original_class_storage->pseudo_methods[$original_method_name]->return_type;
}
$declaring_method_id = $this->getDeclaringMethodId($method_id);
if (!$declaring_method_id) {
return null;
}
$appearing_method_id = $this->getAppearingMethodId($method_id);
if (!$appearing_method_id) {
$class_storage = $this->classlike_storage_provider->get($original_fq_class_name);
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]);
} else {
return null;
}
}
$appearing_fq_class_name = $appearing_method_id->fq_class_name;
$appearing_method_name = $appearing_method_id->method_name;
$appearing_fq_class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name);
if ($appearing_fq_class_name === 'UnitEnum'
&& $original_method_name === 'cases'
&& $original_class_storage->is_enum
&& $original_class_storage->enum_cases
) {
$types = [];
foreach ($original_class_storage->enum_cases as $case_name => $_) {
$types[] = new Type\Union([new Type\Atomic\TEnumCase($original_fq_class_name, $case_name)]);
}
$list = new Type\Atomic\TKeyedArray($types);
$list->is_list = true;
$list->sealed = true;
return new Type\Union([$list]);
}
if (!$appearing_fq_class_storage->user_defined
&& !$appearing_fq_class_storage->stubbed
&& InternalCallMapHandler::inCallMap((string) $appearing_method_id)
) {
if ((string) $appearing_method_id === 'Closure::fromcallable'
&& isset($args[0])
&& $source_analyzer
&& ($first_arg_type = $source_analyzer->getNodeTypeProvider()->getType($args[0]->value))
&& $first_arg_type->isSingle()
) {
foreach ($first_arg_type->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof Type\Atomic\TCallable
|| $atomic_type instanceof Type\Atomic\TClosure
) {
$callable_type = clone $atomic_type;
return new Type\Union([new Type\Atomic\TClosure(
'Closure',
$callable_type->params,
$callable_type->return_type
)]);
}
if ($atomic_type instanceof Type\Atomic\TNamedObject
&& $this->methodExists(
new MethodIdentifier($atomic_type->value, '__invoke')
)
) {
$invokable_storage = $this->getStorage(
new MethodIdentifier($atomic_type->value, '__invoke')
);
return new Type\Union([new Type\Atomic\TClosure(
'Closure',
$invokable_storage->params,
$invokable_storage->return_type
)]);
}
}
}
$callmap_callables = InternalCallMapHandler::getCallablesFromCallMap((string) $appearing_method_id);
if (!$callmap_callables || $callmap_callables[0]->return_type === null) {
throw new \UnexpectedValueException('Shouldnt get here');
}
$return_type_candidate = $callmap_callables[0]->return_type;
if ($return_type_candidate->isFalsable()) {
$return_type_candidate->ignore_falsable_issues = true;
}
return $return_type_candidate;
}
$class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name);
$storage = $this->getStorage($declaring_method_id);
$candidate_type = $storage->return_type;
if ($candidate_type && $candidate_type->isVoid()) {
return clone $candidate_type;
}
if (isset($class_storage->documenting_method_ids[$appearing_method_name])) {
$overridden_method_id = $class_storage->documenting_method_ids[$appearing_method_name];
// special override to allow inference of Iterator types
if ($overridden_method_id->fq_class_name === 'Iterator'
&& $storage->return_type
&& $storage->return_type === $storage->signature_return_type
) {
return clone $storage->return_type;
}
$overridden_storage = $this->getStorage($overridden_method_id);
if ($overridden_storage->return_type) {
if ($overridden_storage->return_type->isNull()) {
return Type::getVoid();
}
if (!$candidate_type || !$source_analyzer) {
$self_class = $overridden_method_id->fq_class_name;
return clone $overridden_storage->return_type;
}
if ($candidate_type->getId() === $overridden_storage->return_type->getId()) {
$self_class = $appearing_fq_class_storage->name;
return clone $candidate_type;
}
$overridden_class_storage =
$this->classlike_storage_provider->get($overridden_method_id->fq_class_name);
$overridden_storage_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion(
$source_analyzer->getCodebase(),
clone $overridden_storage->return_type,
$overridden_method_id->fq_class_name,
$appearing_fq_class_name,
$overridden_class_storage->parent_class,
true,
false,
$storage->final
);
$old_contained_by_new = UnionTypeComparator::isContainedBy(
$source_analyzer->getCodebase(),
$candidate_type,
$overridden_storage_return_type
);
$new_contained_by_old = UnionTypeComparator::isContainedBy(
$source_analyzer->getCodebase(),
$overridden_storage_return_type,
$candidate_type
);
if ((!$old_contained_by_new && !$new_contained_by_old)
|| ($old_contained_by_new && $new_contained_by_old)
) {
if ($old_contained_by_new && $new_contained_by_old) {
$attempted_intersection = Type::intersectUnionTypes(
$candidate_type,
$overridden_storage->return_type,
$source_analyzer->getCodebase()
);
} else {
$attempted_intersection = Type::intersectUnionTypes(
$overridden_storage->return_type,
$candidate_type,
$source_analyzer->getCodebase()
);
}
if ($attempted_intersection) {
$self_class = $overridden_method_id->fq_class_name;
return $attempted_intersection;
}
$self_class = $appearing_fq_class_storage->name;
return clone $candidate_type;
}
if ($old_contained_by_new) {
$self_class = $appearing_fq_class_storage->name;
return clone $candidate_type;
}
$self_class = $overridden_method_id->fq_class_name;
return clone $overridden_storage->return_type;
}
}
if ($candidate_type) {
$self_class = $appearing_fq_class_storage->name;
return clone $candidate_type;
}
if (!isset($class_storage->overridden_method_ids[$appearing_method_name])) {
return null;
}
$candidate_type = 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()) {
if ($candidate_type && !$candidate_type->isVoid()) {
return null;
}
$candidate_type = Type::getVoid();
continue;
}
$fq_overridden_class = $overridden_method_id->fq_class_name;
$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;
if ($candidate_type && $source_analyzer && !$candidate_type->isMixed()) {
$old_contained_by_new = UnionTypeComparator::isContainedBy(
$source_analyzer->getCodebase(),
$candidate_type,
$overridden_return_type
);
$new_contained_by_old = UnionTypeComparator::isContainedBy(
$source_analyzer->getCodebase(),
$overridden_return_type,
$candidate_type
);
if ((!$old_contained_by_new && !$new_contained_by_old)
|| ($old_contained_by_new && $new_contained_by_old)
) {
$attempted_intersection = Type::intersectUnionTypes(
$candidate_type,
$overridden_return_type,
$source_analyzer->getCodebase()
);
if ($attempted_intersection) {
$candidate_type = $attempted_intersection;
continue;
}
return null;
}
if ($old_contained_by_new) {
continue;
}
}
$candidate_type = $overridden_return_type;
}
}
return $candidate_type;
}
public function getMethodReturnsByRef(MethodIdentifier $method_id): bool
{
$method_id = $this->getDeclaringMethodId($method_id);
if (!$method_id) {
return false;
}
$fq_class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name);
if (!$fq_class_storage->user_defined && InternalCallMapHandler::inCallMap((string) $method_id)) {
return false;
}
$storage = $this->getStorage($method_id);
return $storage->returns_by_ref;
}
/**
* @param CodeLocation|null $defined_location
*
*/
public function getMethodReturnTypeLocation(
MethodIdentifier $method_id,
CodeLocation &$defined_location = null
): ?CodeLocation {
$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 lowercase-string $method_name_lc
* @param lowercase-string $declaring_method_name_lc
*
*/
public function setDeclaringMethodId(
string $fq_class_name,
string $method_name_lc,
string $declaring_fq_class_name,
string $declaring_method_name_lc
): void {
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
$class_storage->declaring_method_ids[$method_name_lc] = new MethodIdentifier(
$declaring_fq_class_name,
$declaring_method_name_lc
);
}
/**
* @param lowercase-string $method_name_lc
* @param lowercase-string $appearing_method_name_lc
*
*/
public function setAppearingMethodId(
string $fq_class_name,
string $method_name_lc,
string $appearing_fq_class_name,
string $appearing_method_name_lc
): void {
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
$class_storage->appearing_method_ids[$method_name_lc] = new MethodIdentifier(
$appearing_fq_class_name,
$appearing_method_name_lc
);
}
public function getDeclaringMethodId(
MethodIdentifier $method_id
) : ?MethodIdentifier {
$fq_class_name = $this->classlikes->getUnAliasedName($method_id->fq_class_name);
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
$method_name = $method_id->method_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 reset($class_storage->overridden_method_ids[$method_name]);
}
return null;
}
/**
* Get the class this method appears in (vs is declared in, which could give a trait
*/
public function getAppearingMethodId(
MethodIdentifier $method_id
) : ?MethodIdentifier {
$fq_class_name = $this->classlikes->getUnAliasedName($method_id->fq_class_name);
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
$method_name = $method_id->method_name;
if (isset($class_storage->appearing_method_ids[$method_name])) {
return $class_storage->appearing_method_ids[$method_name];
}
return null;
}
/**
* @return array<string, MethodIdentifier>
*/
public function getOverriddenMethodIds(MethodIdentifier $method_id): array
{
$class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name);
$method_name = $method_id->method_name;
if (isset($class_storage->overridden_method_ids[$method_name])) {
return $class_storage->overridden_method_ids[$method_name];
}
return [];
}
public function getCasedMethodId(MethodIdentifier $original_method_id): string
{
$method_id = $this->getDeclaringMethodId($original_method_id);
if ($method_id === null) {
return (string) $original_method_id;
}
$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;
$storage = $this->getStorage($method_id);
if ($old_method_name === $new_method_name
&& strtolower($old_fq_class_name) !== $old_fq_class_name
) {
return $old_fq_class_name . '::' . $storage->cased_name;
}
return $fq_class_name . '::' . $storage->cased_name;
}
public function getUserMethodStorage(MethodIdentifier $method_id): ?MethodStorage
{
$declaring_method_id = $this->getDeclaringMethodId($method_id);
if (!$declaring_method_id) {
if (\Psalm\Internal\Codebase\InternalCallMapHandler::inCallMap((string) $method_id)) {
return null;
}
throw new \UnexpectedValueException('$storage should not be null for ' . $method_id);
}
$storage = $this->getStorage($declaring_method_id);
if (!$storage->location) {
return null;
}
return $storage;
}
public function getClassLikeStorageForMethod(MethodIdentifier $method_id): ClassLikeStorage
{
$fq_class_name = $method_id->fq_class_name;
$method_name = $method_id->method_name;
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);
}
}
$declaring_method_id = $this->getDeclaringMethodId($method_id);
if ($declaring_method_id === null) {
if (InternalCallMapHandler::inCallMap((string) $method_id)) {
$declaring_method_id = $method_id;
} else {
throw new \UnexpectedValueException('$storage should not be null for ' . $method_id);
}
}
$declaring_fq_class_name = $declaring_method_id->fq_class_name;
return $this->classlike_storage_provider->get($declaring_fq_class_name);
}
public function getStorage(MethodIdentifier $method_id): MethodStorage
{
try {
$class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name);
} catch (\InvalidArgumentException $e) {
throw new \UnexpectedValueException($e->getMessage());
}
$method_name = $method_id->method_name;
if (!isset($class_storage->methods[$method_name])) {
throw new \UnexpectedValueException(
'$storage should not be null for ' . $method_id
);
}
return $class_storage->methods[$method_name];
}
public function hasStorage(MethodIdentifier $method_id): bool
{
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;
}
}