2018-02-09 00:14:28 +01:00
|
|
|
<?php
|
2018-11-12 16:46:55 +01:00
|
|
|
namespace Psalm\Internal\Codebase;
|
2018-02-09 00:14:28 +01:00
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
use function explode;
|
|
|
|
use function preg_replace;
|
2018-02-09 00:14:28 +01:00
|
|
|
use Psalm\CodeLocation;
|
2019-03-01 14:57:10 +01:00
|
|
|
use Psalm\Context;
|
2019-07-05 22:24:00 +02:00
|
|
|
use Psalm\Internal\Provider\ClassLikeStorageProvider;
|
|
|
|
use Psalm\Internal\Provider\FileReferenceProvider;
|
|
|
|
use Psalm\Internal\Provider\PropertyExistenceProvider;
|
|
|
|
use Psalm\Internal\Provider\PropertyTypeProvider;
|
|
|
|
use Psalm\Internal\Provider\PropertyVisibilityProvider;
|
2019-03-01 14:57:10 +01:00
|
|
|
use Psalm\StatementsSource;
|
2019-01-19 16:09:26 +01:00
|
|
|
use Psalm\Type;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function strtolower;
|
2018-02-09 00:14:28 +01:00
|
|
|
|
2018-02-09 23:51:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*
|
|
|
|
* Handles information about class properties
|
|
|
|
*/
|
2018-02-09 00:14:28 +01:00
|
|
|
class Properties
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @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;
|
|
|
|
|
2019-03-01 14:57:10 +01:00
|
|
|
/**
|
|
|
|
* @var PropertyExistenceProvider
|
|
|
|
*/
|
|
|
|
public $property_existence_provider;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var PropertyTypeProvider
|
|
|
|
*/
|
|
|
|
public $property_type_provider;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var PropertyVisibilityProvider
|
|
|
|
*/
|
|
|
|
public $property_visibility_provider;
|
|
|
|
|
2018-02-09 00:14:28 +01:00
|
|
|
public function __construct(
|
2018-09-28 22:18:45 +02:00
|
|
|
ClassLikeStorageProvider $storage_provider,
|
|
|
|
FileReferenceProvider $file_reference_provider
|
2018-02-09 00:14:28 +01:00
|
|
|
) {
|
|
|
|
$this->classlike_storage_provider = $storage_provider;
|
2018-09-28 22:18:45 +02:00
|
|
|
$this->file_reference_provider = $file_reference_provider;
|
2019-03-01 14:57:10 +01:00
|
|
|
$this->property_existence_provider = new PropertyExistenceProvider();
|
|
|
|
$this->property_visibility_provider = new PropertyVisibilityProvider();
|
|
|
|
$this->property_type_provider = new PropertyTypeProvider();
|
2018-02-09 00:14:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether or not a given property exists
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function propertyExists(
|
2019-03-01 14:57:10 +01:00
|
|
|
string $property_id,
|
|
|
|
bool $read_mode,
|
|
|
|
StatementsSource $source = null,
|
|
|
|
Context $context = null,
|
2018-02-09 00:14:28 +01:00
|
|
|
CodeLocation $code_location = null
|
|
|
|
) {
|
|
|
|
// remove trailing backslash if it exists
|
|
|
|
$property_id = preg_replace('/^\\\\/', '', $property_id);
|
|
|
|
|
|
|
|
list($fq_class_name, $property_name) = explode('::$', $property_id);
|
|
|
|
|
2019-03-01 14:57:10 +01:00
|
|
|
if ($this->property_existence_provider->has($fq_class_name)) {
|
|
|
|
$property_exists = $this->property_existence_provider->doesPropertyExist(
|
|
|
|
$fq_class_name,
|
|
|
|
$property_name,
|
|
|
|
$read_mode,
|
|
|
|
$source,
|
|
|
|
$context,
|
|
|
|
$code_location
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($property_exists !== null) {
|
|
|
|
return $property_exists;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-09 00:14:28 +01:00
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
if (isset($class_storage->declaring_property_ids[$property_name])) {
|
2018-09-26 00:37:24 +02:00
|
|
|
$declaring_property_class = $class_storage->declaring_property_ids[$property_name];
|
|
|
|
|
2019-03-01 14:57:10 +01:00
|
|
|
if ($context && $context->calling_method_id) {
|
2019-04-16 22:07:48 +02:00
|
|
|
$this->file_reference_provider->addMethodReferenceToClassMember(
|
2019-03-01 14:57:10 +01:00
|
|
|
$context->calling_method_id,
|
2018-09-26 00:37:24 +02:00
|
|
|
strtolower($declaring_property_class) . '::$' . $property_name
|
|
|
|
);
|
2019-04-16 22:07:48 +02:00
|
|
|
} elseif ($source) {
|
|
|
|
$this->file_reference_provider->addFileReferenceToClassMember(
|
|
|
|
$source->getFilePath(),
|
2019-04-13 00:28:07 +02:00
|
|
|
strtolower($declaring_property_class) . '::$' . $property_name
|
|
|
|
);
|
|
|
|
}
|
2018-02-09 00:14:28 +01:00
|
|
|
|
2019-04-13 00:28:07 +02:00
|
|
|
if ($this->collect_locations && $code_location) {
|
|
|
|
$this->file_reference_provider->addCallingLocationForClassProperty(
|
|
|
|
$code_location,
|
|
|
|
strtolower($declaring_property_class) . '::$' . $property_name
|
|
|
|
);
|
2018-02-09 00:14:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-03-01 14:57:10 +01:00
|
|
|
if ($context && $context->calling_method_id) {
|
2019-04-16 22:07:48 +02:00
|
|
|
$this->file_reference_provider->addMethodReferenceToMissingClassMember(
|
2019-03-01 14:57:10 +01:00
|
|
|
$context->calling_method_id,
|
2018-11-16 00:09:57 +01:00
|
|
|
strtolower($fq_class_name) . '::$' . $property_name
|
|
|
|
);
|
2019-04-16 22:07:48 +02:00
|
|
|
} elseif ($source) {
|
|
|
|
$this->file_reference_provider->addFileReferenceToMissingClassMember(
|
|
|
|
$source->getFilePath(),
|
|
|
|
strtolower($fq_class_name) . '::$' . $property_name
|
|
|
|
);
|
2018-11-16 00:09:57 +01:00
|
|
|
}
|
|
|
|
|
2018-02-09 00:14:28 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $property_id
|
|
|
|
*
|
|
|
|
* @return string|null
|
|
|
|
*/
|
2019-03-01 14:57:10 +01:00
|
|
|
public function getDeclaringClassForProperty($property_id, bool $read_mode)
|
2018-02-09 00:14:28 +01:00
|
|
|
{
|
|
|
|
list($fq_class_name, $property_name) = explode('::$', $property_id);
|
|
|
|
|
2019-03-01 14:57:10 +01:00
|
|
|
if ($this->property_existence_provider->has($fq_class_name)) {
|
|
|
|
if ($this->property_existence_provider->doesPropertyExist(
|
|
|
|
$fq_class_name,
|
|
|
|
$property_name,
|
|
|
|
$read_mode,
|
|
|
|
null,
|
|
|
|
null
|
|
|
|
)) {
|
|
|
|
return $fq_class_name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-09 00:14:28 +01:00
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
if (isset($class_storage->declaring_property_ids[$property_name])) {
|
2018-07-22 01:56:26 +02:00
|
|
|
return $class_storage->declaring_property_ids[$property_name];
|
2018-02-09 00:14:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the class this property appears in (vs is declared in, which could give a trait)
|
|
|
|
*
|
|
|
|
* @param string $property_id
|
|
|
|
*
|
|
|
|
* @return string|null
|
|
|
|
*/
|
2019-03-01 14:57:10 +01:00
|
|
|
public function getAppearingClassForProperty($property_id, bool $read_mode)
|
2018-02-09 00:14:28 +01:00
|
|
|
{
|
|
|
|
list($fq_class_name, $property_name) = explode('::$', $property_id);
|
|
|
|
|
2019-03-01 14:57:10 +01:00
|
|
|
if ($this->property_existence_provider->has($fq_class_name)) {
|
|
|
|
if ($this->property_existence_provider->doesPropertyExist(
|
|
|
|
$fq_class_name,
|
|
|
|
$property_name,
|
|
|
|
$read_mode,
|
|
|
|
null,
|
|
|
|
null
|
|
|
|
)) {
|
|
|
|
return $fq_class_name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-09 00:14:28 +01:00
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
if (isset($class_storage->appearing_property_ids[$property_name])) {
|
|
|
|
$appearing_property_id = $class_storage->appearing_property_ids[$property_name];
|
|
|
|
|
|
|
|
return explode('::$', $appearing_property_id)[0];
|
|
|
|
}
|
|
|
|
}
|
2018-10-26 22:17:15 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $property_id
|
2019-07-05 22:24:00 +02:00
|
|
|
*
|
2018-10-26 22:17:15 +02:00
|
|
|
* @return \Psalm\Storage\PropertyStorage
|
|
|
|
*/
|
|
|
|
public function getStorage($property_id)
|
|
|
|
{
|
|
|
|
// remove trailing backslash if it exists
|
|
|
|
$property_id = preg_replace('/^\\\\/', '', $property_id);
|
|
|
|
|
|
|
|
list($fq_class_name, $property_name) = explode('::$', $property_id);
|
|
|
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
if (isset($class_storage->declaring_property_ids[$property_name])) {
|
|
|
|
$declaring_property_class = $class_storage->declaring_property_ids[$property_name];
|
|
|
|
$declaring_class_storage = $this->classlike_storage_provider->get($declaring_property_class);
|
|
|
|
|
|
|
|
if (isset($declaring_class_storage->properties[$property_name])) {
|
|
|
|
return $declaring_class_storage->properties[$property_name];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new \UnexpectedValueException('Property ' . $property_id . ' should exist');
|
|
|
|
}
|
2019-01-19 16:09:26 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $property_id
|
2019-07-05 22:24:00 +02:00
|
|
|
*
|
2019-01-19 16:09:26 +01:00
|
|
|
* @return ?Type\Union
|
|
|
|
*/
|
2019-03-01 14:57:10 +01:00
|
|
|
public function getPropertyType(
|
|
|
|
$property_id,
|
|
|
|
bool $property_set,
|
|
|
|
StatementsSource $source = null,
|
|
|
|
Context $context = null
|
|
|
|
) {
|
2019-01-19 16:09:26 +01:00
|
|
|
// remove trailing backslash if it exists
|
|
|
|
$property_id = preg_replace('/^\\\\/', '', $property_id);
|
|
|
|
|
|
|
|
list($fq_class_name, $property_name) = explode('::$', $property_id);
|
|
|
|
|
2019-03-01 14:57:10 +01:00
|
|
|
if ($this->property_type_provider->has($fq_class_name)) {
|
|
|
|
$property_type = $this->property_type_provider->getPropertyType(
|
|
|
|
$fq_class_name,
|
|
|
|
$property_name,
|
|
|
|
!$property_set,
|
|
|
|
$source,
|
|
|
|
$context
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($property_type !== null) {
|
|
|
|
return $property_type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-19 16:09:26 +01:00
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
if (isset($class_storage->declaring_property_ids[$property_name])) {
|
|
|
|
$declaring_property_class = $class_storage->declaring_property_ids[$property_name];
|
|
|
|
$declaring_class_storage = $this->classlike_storage_provider->get($declaring_property_class);
|
|
|
|
|
|
|
|
if (isset($declaring_class_storage->properties[$property_name])) {
|
|
|
|
$storage = $declaring_class_storage->properties[$property_name];
|
|
|
|
} else {
|
|
|
|
throw new \UnexpectedValueException('Property ' . $property_id . ' should exist');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new \UnexpectedValueException('Property ' . $property_id . ' should exist');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($storage->type) {
|
2019-01-19 20:26:27 +01:00
|
|
|
if ($property_set) {
|
|
|
|
if (isset($class_storage->pseudo_property_set_types[$property_name])) {
|
|
|
|
return $class_storage->pseudo_property_set_types[$property_name];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (isset($class_storage->pseudo_property_get_types[$property_name])) {
|
|
|
|
return $class_storage->pseudo_property_get_types[$property_name];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-19 16:09:26 +01:00
|
|
|
return $storage->type;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($class_storage->overridden_property_ids[$property_name])) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($class_storage->overridden_property_ids[$property_name] as $overridden_property_id) {
|
|
|
|
$overridden_storage = $this->getStorage($overridden_property_id);
|
|
|
|
|
|
|
|
if ($overridden_storage->type) {
|
|
|
|
return $overridden_storage->type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
2018-02-09 00:14:28 +01:00
|
|
|
}
|