2016-06-14 07:23:57 +02:00
|
|
|
<?php
|
2016-07-26 00:37:44 +02:00
|
|
|
namespace Psalm;
|
2016-06-14 07:23:57 +02:00
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
use function array_merge;
|
|
|
|
use function array_pop;
|
|
|
|
use function array_shift;
|
|
|
|
use function array_values;
|
|
|
|
use function explode;
|
|
|
|
use function implode;
|
|
|
|
use function preg_quote;
|
|
|
|
use function preg_replace;
|
2020-07-22 01:40:35 +02:00
|
|
|
use Psalm\Internal\Type\Comparator\AtomicTypeComparator;
|
2019-07-05 22:24:00 +02:00
|
|
|
use Psalm\Internal\Type\TypeCombination;
|
2020-05-14 01:12:45 +02:00
|
|
|
use Psalm\Internal\Type\TypeParser;
|
|
|
|
use Psalm\Internal\Type\TypeTokenizer;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\Type\Atomic\TArray;
|
2019-01-05 06:15:53 +01:00
|
|
|
use Psalm\Type\Atomic\TArrayKey;
|
2017-01-15 01:06:58 +01:00
|
|
|
use Psalm\Type\Atomic\TBool;
|
2018-03-05 22:06:06 +01:00
|
|
|
use Psalm\Type\Atomic\TClassString;
|
2017-01-15 01:06:58 +01:00
|
|
|
use Psalm\Type\Atomic\TEmpty;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\Type\Atomic\TFalse;
|
|
|
|
use Psalm\Type\Atomic\TFloat;
|
|
|
|
use Psalm\Type\Atomic\TInt;
|
2019-01-04 20:54:40 +01:00
|
|
|
use Psalm\Type\Atomic\TIterable;
|
2019-10-09 00:44:46 +02:00
|
|
|
use Psalm\Type\Atomic\TList;
|
2018-05-21 18:40:39 +02:00
|
|
|
use Psalm\Type\Atomic\TLiteralClassString;
|
2018-05-05 23:30:18 +02:00
|
|
|
use Psalm\Type\Atomic\TLiteralFloat;
|
|
|
|
use Psalm\Type\Atomic\TLiteralInt;
|
|
|
|
use Psalm\Type\Atomic\TLiteralString;
|
2017-01-15 01:06:58 +01:00
|
|
|
use Psalm\Type\Atomic\TMixed;
|
|
|
|
use Psalm\Type\Atomic\TNamedObject;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\Type\Atomic\TNull;
|
2018-02-16 01:50:50 +01:00
|
|
|
use Psalm\Type\Atomic\TNumeric;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\Type\Atomic\TObject;
|
2019-01-18 06:56:24 +01:00
|
|
|
use Psalm\Type\Atomic\TObjectWithProperties;
|
2017-12-29 18:29:36 +01:00
|
|
|
use Psalm\Type\Atomic\TResource;
|
2019-12-09 23:42:22 +01:00
|
|
|
use Psalm\Type\Atomic\TScalar;
|
2018-08-21 17:40:29 +02:00
|
|
|
use Psalm\Type\Atomic\TSingleLetter;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\Type\Atomic\TString;
|
2019-07-05 22:24:00 +02:00
|
|
|
use Psalm\Type\Atomic\TTemplateParam;
|
2017-12-09 20:53:39 +01:00
|
|
|
use Psalm\Type\Atomic\TTrue;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\Type\Atomic\TVoid;
|
2016-11-02 07:29:00 +01:00
|
|
|
use Psalm\Type\Union;
|
2019-07-05 22:24:00 +02:00
|
|
|
use function stripos;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function strlen;
|
|
|
|
use function strpos;
|
2019-07-05 22:24:00 +02:00
|
|
|
use function strtolower;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function substr;
|
2016-06-14 07:23:57 +02:00
|
|
|
|
|
|
|
abstract class Type
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Parses a string type representation
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2019-02-07 18:25:57 +01:00
|
|
|
* @param array{int,int}|null $php_version
|
2019-03-22 20:59:10 +01:00
|
|
|
* @param array<string, array<string, array{Type\Union}>> $template_type_map
|
2016-06-14 07:23:57 +02:00
|
|
|
*/
|
2018-08-28 18:37:25 +02:00
|
|
|
public static function parseString(
|
2020-09-07 01:36:47 +02:00
|
|
|
string $type_string,
|
|
|
|
?array $php_version = null,
|
2018-12-18 05:29:27 +01:00
|
|
|
array $template_type_map = []
|
2020-09-04 22:26:33 +02:00
|
|
|
): Union {
|
2020-05-14 01:12:45 +02:00
|
|
|
return TypeParser::parseTokens(
|
|
|
|
TypeTokenizer::tokenize(
|
|
|
|
$type_string
|
|
|
|
),
|
2020-04-08 15:35:53 +02:00
|
|
|
$php_version,
|
|
|
|
$template_type_map
|
|
|
|
);
|
2018-02-04 18:23:32 +01:00
|
|
|
}
|
|
|
|
|
2019-06-21 05:38:10 +02:00
|
|
|
public static function getFQCLNFromString(
|
|
|
|
string $class,
|
|
|
|
Aliases $aliases
|
|
|
|
) : string {
|
2018-06-14 19:49:16 +02:00
|
|
|
if ($class === '') {
|
2018-02-04 18:23:32 +01:00
|
|
|
throw new \InvalidArgumentException('$class cannot be empty');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($class[0] === '\\') {
|
|
|
|
return substr($class, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
$imported_namespaces = $aliases->uses;
|
|
|
|
|
|
|
|
if (strpos($class, '\\') !== false) {
|
|
|
|
$class_parts = explode('\\', $class);
|
|
|
|
$first_namespace = array_shift($class_parts);
|
|
|
|
|
|
|
|
if (isset($imported_namespaces[strtolower($first_namespace)])) {
|
|
|
|
return $imported_namespaces[strtolower($first_namespace)] . '\\' . implode('\\', $class_parts);
|
|
|
|
}
|
|
|
|
} elseif (isset($imported_namespaces[strtolower($class)])) {
|
|
|
|
return $imported_namespaces[strtolower($class)];
|
|
|
|
}
|
|
|
|
|
|
|
|
$namespace = $aliases->namespace;
|
|
|
|
|
|
|
|
return ($namespace ? $namespace . '\\' : '') . $class;
|
|
|
|
}
|
|
|
|
|
2019-06-01 02:21:34 +02:00
|
|
|
/**
|
2020-08-23 19:52:31 +02:00
|
|
|
* @param array<string, string> $aliased_classes
|
|
|
|
*
|
|
|
|
* @psalm-pure
|
2019-06-01 02:21:34 +02:00
|
|
|
*/
|
|
|
|
public static function getStringFromFQCLN(
|
|
|
|
string $value,
|
|
|
|
?string $namespace,
|
|
|
|
array $aliased_classes,
|
2019-11-17 01:59:08 +01:00
|
|
|
?string $this_class,
|
|
|
|
bool $allow_self = false
|
2019-06-01 02:21:34 +02:00
|
|
|
) : string {
|
2019-11-17 01:59:08 +01:00
|
|
|
if ($allow_self && $value === $this_class) {
|
2019-06-01 02:21:34 +02:00
|
|
|
return 'self';
|
|
|
|
}
|
|
|
|
|
2019-06-11 16:33:52 +02:00
|
|
|
if (isset($aliased_classes[strtolower($value)])) {
|
|
|
|
return $aliased_classes[strtolower($value)];
|
|
|
|
}
|
|
|
|
|
2019-06-01 02:21:34 +02:00
|
|
|
if ($namespace && stripos($value, $namespace . '\\') === 0) {
|
2019-06-02 07:10:50 +02:00
|
|
|
$candidate = preg_replace(
|
2019-06-01 02:21:34 +02:00
|
|
|
'/^' . preg_quote($namespace . '\\') . '/i',
|
|
|
|
'',
|
|
|
|
$value
|
|
|
|
);
|
|
|
|
|
2019-06-02 07:10:50 +02:00
|
|
|
$candidate_parts = explode('\\', $candidate);
|
|
|
|
|
|
|
|
if (!isset($aliased_classes[strtolower($candidate_parts[0])])) {
|
|
|
|
return $candidate;
|
|
|
|
}
|
2020-09-20 14:55:28 +02:00
|
|
|
} elseif (!$namespace && strpos($value, '\\') === false) {
|
2019-06-01 02:21:34 +02:00
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
2019-06-04 22:36:32 +02:00
|
|
|
if (strpos($value, '\\')) {
|
|
|
|
$parts = explode('\\', $value);
|
|
|
|
|
|
|
|
$suffix = array_pop($parts);
|
|
|
|
|
|
|
|
while ($parts) {
|
|
|
|
$left = implode('\\', $parts);
|
|
|
|
|
|
|
|
if (isset($aliased_classes[strtolower($left)])) {
|
|
|
|
return $aliased_classes[strtolower($left)] . '\\' . $suffix;
|
|
|
|
}
|
|
|
|
|
|
|
|
$suffix = array_pop($parts) . '\\' . $suffix;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-01 02:21:34 +02:00
|
|
|
return '\\' . $value;
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
2018-04-25 05:02:20 +02:00
|
|
|
* @param bool $from_calculation
|
2018-05-18 17:02:50 +02:00
|
|
|
* @param int|null $value
|
2018-04-25 05:02:20 +02:00
|
|
|
*
|
2016-11-02 07:29:00 +01:00
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getInt($from_calculation = false, $value = null): Union
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2018-05-18 17:02:50 +02:00
|
|
|
if ($value !== null) {
|
|
|
|
$union = new Union([new TLiteralInt($value)]);
|
2018-05-05 23:30:18 +02:00
|
|
|
} else {
|
|
|
|
$union = new Union([new TInt()]);
|
|
|
|
}
|
|
|
|
|
2018-04-25 05:02:20 +02:00
|
|
|
$union->from_calculation = $from_calculation;
|
2016-06-15 01:22:29 +02:00
|
|
|
|
2018-04-25 05:02:20 +02:00
|
|
|
return $union;
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
|
|
|
|
2020-07-26 21:51:55 +02:00
|
|
|
/**
|
|
|
|
* @param int|null $value
|
|
|
|
*
|
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getPositiveInt(bool $from_calculation = false): Union
|
2020-07-26 21:51:55 +02:00
|
|
|
{
|
2020-08-28 22:38:50 +02:00
|
|
|
$union = new Union([new Type\Atomic\TPositiveInt()]);
|
2020-07-26 21:51:55 +02:00
|
|
|
$union->from_calculation = $from_calculation;
|
|
|
|
|
|
|
|
return $union;
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getNumeric(): Union
|
2018-02-16 01:50:50 +01:00
|
|
|
{
|
|
|
|
$type = new TNumeric;
|
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2020-09-07 01:36:47 +02:00
|
|
|
public static function getString(?string $value = null): Union
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2019-03-07 20:56:18 +01:00
|
|
|
$type = null;
|
|
|
|
|
2018-05-18 17:02:50 +02:00
|
|
|
if ($value !== null) {
|
2019-04-22 16:01:25 +02:00
|
|
|
$config = \Psalm\Config::getInstance();
|
|
|
|
|
2019-05-21 05:14:41 +02:00
|
|
|
if ($config->string_interpreters) {
|
|
|
|
foreach ($config->string_interpreters as $string_interpreter) {
|
|
|
|
if ($type = $string_interpreter::getTypeFromValue($value)) {
|
|
|
|
break;
|
2019-03-14 15:23:26 +01:00
|
|
|
}
|
2019-03-07 20:56:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-13 22:08:51 +02:00
|
|
|
if (!$type) {
|
|
|
|
if (strlen($value) < $config->max_string_length) {
|
|
|
|
$type = new TLiteralString($value);
|
|
|
|
} else {
|
|
|
|
$type = new Type\Atomic\TNonEmptyString();
|
|
|
|
}
|
2019-03-07 20:56:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$type) {
|
2018-05-05 23:30:18 +02:00
|
|
|
$type = new TString();
|
|
|
|
}
|
2016-06-15 01:22:29 +02:00
|
|
|
|
2016-09-09 22:21:49 +02:00
|
|
|
return new Union([$type]);
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getSingleLetter(): Union
|
2018-08-21 17:40:29 +02:00
|
|
|
{
|
|
|
|
$type = new TSingleLetter;
|
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2018-03-05 22:06:06 +01:00
|
|
|
/**
|
2019-01-04 18:28:00 +01:00
|
|
|
* @param string $extends
|
2018-04-04 04:20:00 +02:00
|
|
|
*
|
2018-03-05 22:06:06 +01:00
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getClassString($extends = 'object'): Union
|
2018-03-05 22:06:06 +01:00
|
|
|
{
|
2019-01-20 04:45:58 +01:00
|
|
|
return new Union([
|
|
|
|
new TClassString(
|
|
|
|
$extends,
|
|
|
|
$extends === 'object'
|
|
|
|
? null
|
|
|
|
: new TNamedObject($extends)
|
2019-07-05 22:24:00 +02:00
|
|
|
),
|
2019-01-20 04:45:58 +01:00
|
|
|
]);
|
2019-01-04 18:28:00 +01:00
|
|
|
}
|
2018-05-21 18:40:39 +02:00
|
|
|
|
2020-09-07 01:36:47 +02:00
|
|
|
public static function getLiteralClassString(string $class_type): Union
|
2019-01-04 18:28:00 +01:00
|
|
|
{
|
2018-05-21 18:40:39 +02:00
|
|
|
$type = new TLiteralClassString($class_type);
|
2018-03-05 22:06:06 +01:00
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getNull(): Union
|
2016-06-16 02:16:40 +02:00
|
|
|
{
|
2017-01-15 01:06:58 +01:00
|
|
|
$type = new TNull;
|
2016-06-16 02:16:40 +02:00
|
|
|
|
2016-09-09 22:21:49 +02:00
|
|
|
return new Union([$type]);
|
2016-06-16 02:16:40 +02:00
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
2018-12-08 19:18:55 +01:00
|
|
|
* @param bool $from_loop_isset
|
2018-04-07 00:28:22 +02:00
|
|
|
*
|
2016-11-02 07:29:00 +01:00
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getMixed($from_loop_isset = false): Union
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2018-12-08 19:18:55 +01:00
|
|
|
$type = new TMixed($from_loop_isset);
|
2016-06-15 01:22:29 +02:00
|
|
|
|
2016-09-09 22:21:49 +02:00
|
|
|
return new Union([$type]);
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getScalar(): Union
|
2019-12-09 23:06:00 +01:00
|
|
|
{
|
2019-12-09 23:35:50 +01:00
|
|
|
$type = new TScalar();
|
2019-12-09 23:06:00 +01:00
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getEmpty(): Union
|
2018-05-18 17:02:50 +02:00
|
|
|
{
|
|
|
|
$type = new TEmpty();
|
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getBool(): Union
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2017-01-15 01:06:58 +01:00
|
|
|
$type = new TBool;
|
2016-06-15 01:22:29 +02:00
|
|
|
|
2016-09-09 22:21:49 +02:00
|
|
|
return new Union([$type]);
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
|
|
|
|
2020-09-07 01:36:47 +02:00
|
|
|
public static function getFloat(?float $value = null): Union
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2018-05-18 17:02:50 +02:00
|
|
|
if ($value !== null) {
|
|
|
|
$type = new TLiteralFloat($value);
|
2018-05-05 23:30:18 +02:00
|
|
|
} else {
|
|
|
|
$type = new TFloat();
|
|
|
|
}
|
2016-06-15 01:22:29 +02:00
|
|
|
|
2016-09-09 22:21:49 +02:00
|
|
|
return new Union([$type]);
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getObject(): Union
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2017-01-15 01:06:58 +01:00
|
|
|
$type = new TObject;
|
2016-06-15 01:22:29 +02:00
|
|
|
|
2016-09-09 22:21:49 +02:00
|
|
|
return new Union([$type]);
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getClosure(): Union
|
2016-10-21 00:05:28 +02:00
|
|
|
{
|
2020-08-30 04:02:58 +02:00
|
|
|
$type = new Type\Atomic\TFn('Closure');
|
2016-10-21 00:05:28 +02:00
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getArrayKey(): Union
|
2019-01-05 06:15:53 +01:00
|
|
|
{
|
|
|
|
$type = new TArrayKey();
|
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getArray(): Union
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2017-01-15 01:06:58 +01:00
|
|
|
$type = new TArray(
|
2016-09-09 22:21:49 +02:00
|
|
|
[
|
2019-01-05 06:15:53 +01:00
|
|
|
new Type\Union([new TArrayKey]),
|
2017-05-27 02:05:57 +02:00
|
|
|
new Type\Union([new TMixed]),
|
2016-09-09 22:21:49 +02:00
|
|
|
]
|
|
|
|
);
|
2016-06-15 01:22:29 +02:00
|
|
|
|
2016-09-09 22:21:49 +02:00
|
|
|
return new Union([$type]);
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getEmptyArray(): Union
|
2016-09-12 06:02:26 +02:00
|
|
|
{
|
2018-05-03 19:56:30 +02:00
|
|
|
$array_type = new TArray(
|
|
|
|
[
|
|
|
|
new Type\Union([new TEmpty]),
|
|
|
|
new Type\Union([new TEmpty]),
|
|
|
|
]
|
|
|
|
);
|
|
|
|
|
2016-09-12 06:02:26 +02:00
|
|
|
return new Type\Union([
|
2018-05-03 19:56:30 +02:00
|
|
|
$array_type,
|
2016-09-12 06:02:26 +02:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getList(): Union
|
2019-10-10 16:57:43 +02:00
|
|
|
{
|
|
|
|
$type = new TList(new Type\Union([new TMixed]));
|
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getNonEmptyList(): Union
|
2020-07-30 00:10:43 +02:00
|
|
|
{
|
|
|
|
$type = new Type\Atomic\TNonEmptyList(new Type\Union([new TMixed]));
|
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getVoid(): Union
|
2016-06-16 02:16:40 +02:00
|
|
|
{
|
2017-01-15 01:06:58 +01:00
|
|
|
$type = new TVoid;
|
2016-06-16 02:16:40 +02:00
|
|
|
|
2016-09-09 22:21:49 +02:00
|
|
|
return new Union([$type]);
|
2016-06-16 02:16:40 +02:00
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getFalse(): Union
|
2016-06-16 02:16:40 +02:00
|
|
|
{
|
2017-01-15 01:06:58 +01:00
|
|
|
$type = new TFalse;
|
2016-06-16 02:16:40 +02:00
|
|
|
|
2016-09-09 22:21:49 +02:00
|
|
|
return new Union([$type]);
|
2016-06-16 02:16:40 +02:00
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getTrue(): Union
|
2017-12-09 20:53:39 +01:00
|
|
|
{
|
|
|
|
$type = new TTrue;
|
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public static function getResource(): Union
|
2017-12-29 18:29:36 +01:00
|
|
|
{
|
|
|
|
return new Union([new TResource]);
|
|
|
|
}
|
|
|
|
|
2020-06-25 19:05:34 +02:00
|
|
|
/**
|
|
|
|
* @param non-empty-list<Type\Union> $union_types
|
|
|
|
*/
|
|
|
|
public static function combineUnionTypeArray(array $union_types, ?Codebase $codebase) : Type\Union
|
|
|
|
{
|
|
|
|
$first_type = array_pop($union_types);
|
|
|
|
|
|
|
|
foreach ($union_types as $type) {
|
|
|
|
$first_type = self::combineUnionTypes($first_type, $type, $codebase);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $first_type;
|
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
/**
|
|
|
|
* Combines two union types into one
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2018-11-28 21:12:08 +01:00
|
|
|
* @param int $literal_limit any greater number of literal types than this
|
|
|
|
* will be merged to a scalar
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-06-16 02:16:40 +02:00
|
|
|
*/
|
2018-11-28 16:41:49 +01:00
|
|
|
public static function combineUnionTypes(
|
|
|
|
Union $type_1,
|
|
|
|
Union $type_2,
|
2020-09-07 01:36:47 +02:00
|
|
|
?Codebase $codebase = null,
|
2018-11-28 21:12:08 +01:00
|
|
|
bool $overwrite_empty_array = false,
|
2018-12-08 19:18:55 +01:00
|
|
|
bool $allow_mixed_union = true,
|
2018-11-28 21:12:08 +01:00
|
|
|
int $literal_limit = 500
|
2020-09-04 22:26:33 +02:00
|
|
|
): Union {
|
2019-06-26 06:14:06 +02:00
|
|
|
if ($type_1 === $type_2) {
|
|
|
|
return $type_1;
|
|
|
|
}
|
|
|
|
|
2018-12-08 19:18:55 +01:00
|
|
|
if ($type_1->isVanillaMixed() && $type_2->isVanillaMixed()) {
|
2018-03-23 18:14:00 +01:00
|
|
|
$combined_type = Type::getMixed();
|
|
|
|
} else {
|
2018-12-08 19:18:55 +01:00
|
|
|
$both_failed_reconciliation = false;
|
2017-03-13 23:06:56 +01:00
|
|
|
|
2018-03-23 18:14:00 +01:00
|
|
|
if ($type_1->failed_reconciliation) {
|
|
|
|
if ($type_2->failed_reconciliation) {
|
|
|
|
$both_failed_reconciliation = true;
|
|
|
|
} else {
|
2020-09-30 18:28:13 +02:00
|
|
|
$type_2 = clone $type_2;
|
|
|
|
$type_2->parent_nodes += $type_1->parent_nodes;
|
|
|
|
|
2018-03-23 18:14:00 +01:00
|
|
|
return $type_2;
|
|
|
|
}
|
|
|
|
} elseif ($type_2->failed_reconciliation) {
|
2020-09-30 18:28:13 +02:00
|
|
|
$type_1 = clone $type_1;
|
|
|
|
$type_1->parent_nodes += $type_2->parent_nodes;
|
|
|
|
|
2018-03-23 18:14:00 +01:00
|
|
|
return $type_1;
|
2017-03-13 23:06:56 +01:00
|
|
|
}
|
|
|
|
|
2018-05-20 01:44:03 +02:00
|
|
|
$combined_type = TypeCombination::combineTypes(
|
2018-03-23 18:14:00 +01:00
|
|
|
array_merge(
|
2020-01-04 18:20:26 +01:00
|
|
|
array_values($type_1->getAtomicTypes()),
|
|
|
|
array_values($type_2->getAtomicTypes())
|
2018-11-28 16:41:49 +01:00
|
|
|
),
|
2019-01-05 06:15:53 +01:00
|
|
|
$codebase,
|
2018-11-28 21:12:08 +01:00
|
|
|
$overwrite_empty_array,
|
2018-12-08 19:18:55 +01:00
|
|
|
$allow_mixed_union,
|
2018-11-28 21:12:08 +01:00
|
|
|
$literal_limit
|
2018-03-23 18:14:00 +01:00
|
|
|
);
|
2017-01-27 07:23:12 +01:00
|
|
|
|
2018-03-23 18:14:00 +01:00
|
|
|
if (!$type_1->initialized || !$type_2->initialized) {
|
|
|
|
$combined_type->initialized = false;
|
|
|
|
}
|
2017-01-27 07:23:12 +01:00
|
|
|
|
2018-07-13 20:06:01 +02:00
|
|
|
if ($type_1->possibly_undefined_from_try || $type_2->possibly_undefined_from_try) {
|
|
|
|
$combined_type->possibly_undefined_from_try = true;
|
|
|
|
}
|
|
|
|
|
2018-03-23 18:14:00 +01:00
|
|
|
if ($type_1->from_docblock || $type_2->from_docblock) {
|
|
|
|
$combined_type->from_docblock = true;
|
|
|
|
}
|
2017-03-19 19:39:05 +01:00
|
|
|
|
2018-04-25 05:02:20 +02:00
|
|
|
if ($type_1->from_calculation || $type_2->from_calculation) {
|
|
|
|
$combined_type->from_calculation = true;
|
|
|
|
}
|
|
|
|
|
2018-03-23 18:14:00 +01:00
|
|
|
if ($type_1->ignore_nullable_issues || $type_2->ignore_nullable_issues) {
|
|
|
|
$combined_type->ignore_nullable_issues = true;
|
|
|
|
}
|
2017-05-10 19:36:05 +02:00
|
|
|
|
2018-03-23 18:14:00 +01:00
|
|
|
if ($type_1->ignore_falsable_issues || $type_2->ignore_falsable_issues) {
|
|
|
|
$combined_type->ignore_falsable_issues = true;
|
|
|
|
}
|
2018-01-25 00:52:58 +01:00
|
|
|
|
2019-07-10 20:48:15 +02:00
|
|
|
if ($type_1->had_template && $type_2->had_template) {
|
|
|
|
$combined_type->had_template = true;
|
|
|
|
}
|
|
|
|
|
2020-03-14 06:09:12 +01:00
|
|
|
if ($type_1->reference_free && $type_2->reference_free) {
|
|
|
|
$combined_type->reference_free = true;
|
2019-08-31 15:49:32 +02:00
|
|
|
}
|
|
|
|
|
2018-03-23 18:14:00 +01:00
|
|
|
if ($both_failed_reconciliation) {
|
|
|
|
$combined_type->failed_reconciliation = true;
|
|
|
|
}
|
2017-03-13 23:06:56 +01:00
|
|
|
}
|
|
|
|
|
2018-03-23 06:36:56 +01:00
|
|
|
if ($type_1->possibly_undefined || $type_2->possibly_undefined) {
|
2018-03-17 21:53:11 +01:00
|
|
|
$combined_type->possibly_undefined = true;
|
|
|
|
}
|
|
|
|
|
2020-05-22 04:47:58 +02:00
|
|
|
if ($type_1->parent_nodes || $type_2->parent_nodes) {
|
2020-09-28 06:45:02 +02:00
|
|
|
$combined_type->parent_nodes = $type_1->parent_nodes + $type_2->parent_nodes;
|
2019-08-04 16:37:36 +02:00
|
|
|
}
|
|
|
|
|
2020-09-30 18:28:13 +02:00
|
|
|
if ($type_1->by_ref || $type_2->by_ref) {
|
|
|
|
$combined_type->by_ref = true;
|
|
|
|
}
|
|
|
|
|
2017-01-27 07:23:12 +01:00
|
|
|
return $combined_type;
|
2016-06-16 02:16:40 +02:00
|
|
|
}
|
2019-05-17 00:36:36 +02:00
|
|
|
|
2019-05-24 18:48:37 +02:00
|
|
|
/**
|
|
|
|
* Combines two union types into one via an intersection
|
|
|
|
*
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public static function intersectUnionTypes(
|
|
|
|
Union $type_1,
|
2020-04-07 06:13:56 +02:00
|
|
|
Union $type_2,
|
|
|
|
Codebase $codebase
|
2020-09-04 22:26:33 +02:00
|
|
|
): ?Union {
|
2019-12-20 02:42:57 +01:00
|
|
|
$intersection_performed = false;
|
|
|
|
|
2019-05-24 18:48:37 +02:00
|
|
|
if ($type_1->isMixed() && $type_2->isMixed()) {
|
|
|
|
$combined_type = Type::getMixed();
|
|
|
|
} else {
|
|
|
|
$both_failed_reconciliation = false;
|
|
|
|
|
|
|
|
if ($type_1->failed_reconciliation) {
|
|
|
|
if ($type_2->failed_reconciliation) {
|
|
|
|
$both_failed_reconciliation = true;
|
|
|
|
} else {
|
|
|
|
return $type_2;
|
|
|
|
}
|
|
|
|
} elseif ($type_2->failed_reconciliation) {
|
|
|
|
return $type_1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($type_1->isMixed() && !$type_2->isMixed()) {
|
|
|
|
$combined_type = clone $type_2;
|
2019-12-20 02:42:57 +01:00
|
|
|
$intersection_performed = true;
|
2019-05-24 18:48:37 +02:00
|
|
|
} elseif (!$type_1->isMixed() && $type_2->isMixed()) {
|
|
|
|
$combined_type = clone $type_1;
|
2019-12-20 02:42:57 +01:00
|
|
|
$intersection_performed = true;
|
2019-05-24 18:48:37 +02:00
|
|
|
} else {
|
|
|
|
$combined_type = clone $type_1;
|
|
|
|
|
2020-01-04 18:20:26 +01:00
|
|
|
foreach ($combined_type->getAtomicTypes() as $t1_key => $type_1_atomic) {
|
|
|
|
foreach ($type_2->getAtomicTypes() as $t2_key => $type_2_atomic) {
|
2020-04-07 06:13:56 +02:00
|
|
|
if ($type_1_atomic instanceof TNamedObject
|
|
|
|
&& $type_2_atomic instanceof TNamedObject
|
|
|
|
) {
|
2020-07-22 01:40:35 +02:00
|
|
|
if (AtomicTypeComparator::isContainedBy(
|
2020-04-07 06:13:56 +02:00
|
|
|
$codebase,
|
|
|
|
$type_2_atomic,
|
2020-04-07 17:17:52 +02:00
|
|
|
$type_1_atomic
|
2020-04-07 06:13:56 +02:00
|
|
|
)) {
|
|
|
|
$combined_type->removeType($t1_key);
|
|
|
|
$combined_type->addType(clone $type_2_atomic);
|
|
|
|
$intersection_performed = true;
|
2020-07-22 01:40:35 +02:00
|
|
|
} elseif (AtomicTypeComparator::isContainedBy(
|
2020-04-07 06:13:56 +02:00
|
|
|
$codebase,
|
|
|
|
$type_1_atomic,
|
|
|
|
$type_2_atomic
|
|
|
|
)) {
|
|
|
|
$combined_type->removeType($t2_key);
|
|
|
|
$combined_type->addType(clone $type_1_atomic);
|
|
|
|
$intersection_performed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-24 18:48:37 +02:00
|
|
|
if (($type_1_atomic instanceof TIterable
|
|
|
|
|| $type_1_atomic instanceof TNamedObject
|
2019-06-19 18:00:07 +02:00
|
|
|
|| $type_1_atomic instanceof TTemplateParam
|
|
|
|
|| $type_1_atomic instanceof TObjectWithProperties)
|
2019-05-24 18:48:37 +02:00
|
|
|
&& ($type_2_atomic instanceof TIterable
|
|
|
|
|| $type_2_atomic instanceof TNamedObject
|
2019-06-19 18:00:07 +02:00
|
|
|
|| $type_2_atomic instanceof TTemplateParam
|
|
|
|
|| $type_2_atomic instanceof TObjectWithProperties)
|
2019-05-24 18:48:37 +02:00
|
|
|
) {
|
|
|
|
if (!$type_1_atomic->extra_types) {
|
|
|
|
$type_1_atomic->extra_types = [];
|
|
|
|
}
|
|
|
|
|
2019-12-20 02:42:57 +01:00
|
|
|
$intersection_performed = true;
|
|
|
|
|
2019-05-24 18:48:37 +02:00
|
|
|
$type_2_atomic_clone = clone $type_2_atomic;
|
|
|
|
|
|
|
|
$type_2_atomic_clone->extra_types = [];
|
|
|
|
|
2019-05-28 06:32:17 +02:00
|
|
|
$type_1_atomic->extra_types[$type_2_atomic_clone->getKey()] = $type_2_atomic_clone;
|
2019-05-24 18:48:37 +02:00
|
|
|
|
|
|
|
$type_2_atomic_intersection_types = $type_2_atomic->getIntersectionTypes();
|
|
|
|
|
|
|
|
if ($type_2_atomic_intersection_types) {
|
|
|
|
foreach ($type_2_atomic_intersection_types as $type_2_intersection_type) {
|
2019-05-28 06:32:17 +02:00
|
|
|
$type_1_atomic->extra_types[$type_2_intersection_type->getKey()]
|
|
|
|
= clone $type_2_intersection_type;
|
2019-05-24 18:48:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-26 19:11:43 +02:00
|
|
|
|
|
|
|
if ($type_1_atomic instanceof TObject && $type_2_atomic instanceof TNamedObject) {
|
|
|
|
$combined_type->removeType($t1_key);
|
|
|
|
$combined_type->addType(clone $type_2_atomic);
|
2019-12-20 02:42:57 +01:00
|
|
|
$intersection_performed = true;
|
|
|
|
} elseif ($type_2_atomic instanceof TObject && $type_1_atomic instanceof TNamedObject) {
|
|
|
|
$combined_type->removeType($t2_key);
|
|
|
|
$combined_type->addType(clone $type_1_atomic);
|
|
|
|
$intersection_performed = true;
|
2019-05-26 19:11:43 +02:00
|
|
|
}
|
2019-05-24 18:48:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$type_1->initialized && !$type_2->initialized) {
|
|
|
|
$combined_type->initialized = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($type_1->possibly_undefined_from_try && $type_2->possibly_undefined_from_try) {
|
|
|
|
$combined_type->possibly_undefined_from_try = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($type_1->from_docblock && $type_2->from_docblock) {
|
|
|
|
$combined_type->from_docblock = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($type_1->from_calculation && $type_2->from_calculation) {
|
|
|
|
$combined_type->from_calculation = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($type_1->ignore_nullable_issues && $type_2->ignore_nullable_issues) {
|
|
|
|
$combined_type->ignore_nullable_issues = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($type_1->ignore_falsable_issues && $type_2->ignore_falsable_issues) {
|
|
|
|
$combined_type->ignore_falsable_issues = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($both_failed_reconciliation) {
|
|
|
|
$combined_type->failed_reconciliation = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-20 02:42:57 +01:00
|
|
|
if (!$intersection_performed && $type_1->getId() !== $type_2->getId()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-05-24 18:48:37 +02:00
|
|
|
if ($type_1->possibly_undefined && $type_2->possibly_undefined) {
|
|
|
|
$combined_type->possibly_undefined = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $combined_type;
|
|
|
|
}
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|