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_keys;
|
|
|
|
use function array_map;
|
|
|
|
use function array_merge;
|
|
|
|
use function array_pop;
|
|
|
|
use function array_push;
|
|
|
|
use function array_shift;
|
|
|
|
use function array_splice;
|
|
|
|
use function array_unshift;
|
|
|
|
use function array_values;
|
|
|
|
use function count;
|
|
|
|
use function explode;
|
|
|
|
use function get_class;
|
|
|
|
use function implode;
|
|
|
|
use function in_array;
|
|
|
|
use function is_numeric;
|
|
|
|
use function preg_match;
|
|
|
|
use function preg_quote;
|
|
|
|
use function preg_replace;
|
2016-11-21 05:31:10 +01:00
|
|
|
use Psalm\Exception\TypeParseTreeException;
|
2019-07-05 22:24:00 +02:00
|
|
|
use Psalm\Internal\Type\ParseTree;
|
|
|
|
use Psalm\Internal\Type\TypeCombination;
|
2018-05-12 00:35:02 +02:00
|
|
|
use Psalm\Storage\FunctionLikeParameter;
|
2016-07-26 00:37:44 +02:00
|
|
|
use Psalm\Type\Atomic;
|
2017-01-15 01:06:58 +01:00
|
|
|
use Psalm\Type\Atomic\ObjectLike;
|
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-27 04:13:10 +02:00
|
|
|
use Psalm\Type\Atomic\TCallable;
|
2018-03-05 22:06:06 +01:00
|
|
|
use Psalm\Type\Atomic\TClassString;
|
2019-12-27 18:49:28 +01:00
|
|
|
use Psalm\Type\Atomic\TClassStringMap;
|
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\TGenericObject;
|
|
|
|
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;
|
2019-10-09 00:44:46 +02:00
|
|
|
use Psalm\Type\Atomic\TNonEmptyList;
|
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 str_split;
|
|
|
|
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
|
|
|
|
{
|
2018-03-31 01:04:21 +02:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2019-01-13 20:06:30 +01:00
|
|
|
const PSALM_RESERVED_WORDS = [
|
2018-03-31 01:04:21 +02:00
|
|
|
'int' => true,
|
|
|
|
'string' => true,
|
|
|
|
'float' => true,
|
|
|
|
'bool' => true,
|
|
|
|
'false' => true,
|
2018-04-03 17:24:23 +02:00
|
|
|
'true' => true,
|
2018-03-31 01:04:21 +02:00
|
|
|
'object' => true,
|
|
|
|
'empty' => true,
|
|
|
|
'callable' => true,
|
|
|
|
'array' => true,
|
2019-03-02 14:35:50 +01:00
|
|
|
'non-empty-array' => true,
|
2019-12-13 21:51:43 +01:00
|
|
|
'non-empty-string' => true,
|
2018-03-31 01:04:21 +02:00
|
|
|
'iterable' => true,
|
|
|
|
'null' => true,
|
|
|
|
'mixed' => true,
|
|
|
|
'numeric-string' => true,
|
|
|
|
'class-string' => true,
|
2019-04-10 00:09:57 +02:00
|
|
|
'callable-string' => true,
|
2019-10-20 21:33:57 +02:00
|
|
|
'callable-array' => true,
|
2019-05-31 15:43:46 +02:00
|
|
|
'trait-string' => true,
|
2018-10-29 16:54:25 +01:00
|
|
|
'mysql-escaped-string' => true,
|
|
|
|
'html-escaped-string' => true,
|
2018-03-31 01:04:21 +02:00
|
|
|
'boolean' => true,
|
|
|
|
'integer' => true,
|
|
|
|
'double' => true,
|
|
|
|
'real' => true,
|
|
|
|
'resource' => true,
|
|
|
|
'void' => true,
|
2018-03-31 01:20:38 +02:00
|
|
|
'self' => true,
|
|
|
|
'static' => true,
|
2018-04-03 17:24:23 +02:00
|
|
|
'scalar' => true,
|
2018-04-04 20:42:23 +02:00
|
|
|
'numeric' => true,
|
2019-01-02 23:26:05 +01:00
|
|
|
'no-return' => true,
|
|
|
|
'never-return' => true,
|
|
|
|
'never-returns' => true,
|
2019-01-04 20:54:40 +01:00
|
|
|
'array-key' => true,
|
2019-05-24 04:27:36 +02:00
|
|
|
'key-of' => true,
|
2019-05-28 16:44:04 +02:00
|
|
|
'value-of' => true,
|
2019-06-09 23:54:17 +02:00
|
|
|
'non-empty-countable' => true,
|
2019-10-09 00:44:46 +02:00
|
|
|
'list' => true,
|
2019-10-09 04:53:55 +02:00
|
|
|
'non-empty-list' => true,
|
2019-12-27 18:49:28 +01:00
|
|
|
'class-string-map' => true,
|
2020-01-17 19:12:00 +01:00
|
|
|
'open-resource' => true,
|
|
|
|
'closed-resource' => true,
|
2018-03-31 01:04:21 +02:00
|
|
|
];
|
|
|
|
|
2018-02-04 18:23:32 +01:00
|
|
|
/**
|
2019-11-21 17:03:18 +01:00
|
|
|
* @var array<string, list<array{0: string, 1: int}>>
|
2018-02-04 18:23:32 +01:00
|
|
|
*/
|
|
|
|
private static $memoized_tokens = [];
|
|
|
|
|
2016-06-14 07:23:57 +02:00
|
|
|
/**
|
|
|
|
* Parses a string type representation
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2016-08-04 20:43:33 +02:00
|
|
|
* @param string $type_string
|
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
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-06-28 20:28:45 +02:00
|
|
|
* @return Union
|
2016-06-14 07:23:57 +02:00
|
|
|
*/
|
2018-08-28 18:37:25 +02:00
|
|
|
public static function parseString(
|
|
|
|
$type_string,
|
2019-02-07 18:25:57 +01:00
|
|
|
array $php_version = null,
|
2018-12-18 05:29:27 +01:00
|
|
|
array $template_type_map = []
|
2018-08-28 18:37:25 +02:00
|
|
|
) {
|
2019-02-07 18:25:57 +01:00
|
|
|
return self::parseTokens(self::tokenize($type_string), $php_version, $template_type_map);
|
2018-05-20 23:19:53 +02:00
|
|
|
}
|
2016-06-14 07:23:57 +02:00
|
|
|
|
2018-05-20 23:19:53 +02:00
|
|
|
/**
|
|
|
|
* Parses a string type representation
|
|
|
|
*
|
2019-11-21 16:44:24 +01:00
|
|
|
* @param list<array{0: string, 1: int}> $type_tokens
|
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
|
2018-05-20 23:19:53 +02:00
|
|
|
*
|
|
|
|
* @return Union
|
|
|
|
*/
|
2018-08-28 18:37:25 +02:00
|
|
|
public static function parseTokens(
|
|
|
|
array $type_tokens,
|
2019-02-07 18:25:57 +01:00
|
|
|
array $php_version = null,
|
2018-12-18 05:29:27 +01:00
|
|
|
array $template_type_map = []
|
2018-08-28 18:37:25 +02:00
|
|
|
) {
|
2016-06-14 07:23:57 +02:00
|
|
|
if (count($type_tokens) === 1) {
|
2018-04-25 03:27:31 +02:00
|
|
|
$only_token = $type_tokens[0];
|
2016-06-24 00:45:46 +02:00
|
|
|
|
2018-04-25 03:27:31 +02:00
|
|
|
// Note: valid identifiers can include class names or $this
|
2019-06-23 05:30:40 +02:00
|
|
|
if (!preg_match('@^(\$this|\\\\?[a-zA-Z_\x7f-\xff][\\\\\-0-9a-zA-Z_\x7f-\xff]*)$@', $only_token[0])) {
|
2019-11-21 17:03:18 +01:00
|
|
|
if (!\is_numeric($only_token[0])
|
2019-11-21 16:44:24 +01:00
|
|
|
&& strpos($only_token[0], '\'') !== false
|
|
|
|
&& strpos($only_token[0], '"') !== false
|
|
|
|
) {
|
|
|
|
throw new TypeParseTreeException("Invalid type '$only_token[0]'");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$only_token[0] = self::fixScalarTerms($only_token[0], $php_version);
|
2018-04-25 03:27:31 +02:00
|
|
|
|
2019-11-21 16:44:24 +01:00
|
|
|
$atomic = Atomic::create($only_token[0], $php_version, $template_type_map);
|
|
|
|
$atomic->offset_start = 0;
|
|
|
|
$atomic->offset_end = strlen($only_token[0]);
|
2019-06-23 06:19:41 +02:00
|
|
|
|
2019-11-21 16:44:24 +01:00
|
|
|
return new Union([$atomic]);
|
|
|
|
}
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|
|
|
|
|
2019-02-01 02:21:20 +01:00
|
|
|
$parse_tree = ParseTree::createFromTokens($type_tokens);
|
2019-02-07 18:25:57 +01:00
|
|
|
$parsed_type = self::getTypeFromTree($parse_tree, $php_version, $template_type_map);
|
2016-06-14 07:23:57 +02:00
|
|
|
|
2016-06-28 20:28:45 +02:00
|
|
|
if (!($parsed_type instanceof Union)) {
|
2016-06-15 01:22:29 +02:00
|
|
|
$parsed_type = new Union([$parsed_type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $parsed_type;
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $type_string
|
2019-02-07 18:25:57 +01:00
|
|
|
* @param array{int,int}|null $php_version
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return string
|
|
|
|
*/
|
2019-06-21 05:38:10 +02:00
|
|
|
private static function fixScalarTerms(
|
|
|
|
string $type_string,
|
|
|
|
?array $php_version = null
|
|
|
|
) : string {
|
2018-01-10 06:07:47 +01:00
|
|
|
$type_string_lc = strtolower($type_string);
|
|
|
|
|
|
|
|
switch ($type_string_lc) {
|
|
|
|
case 'int':
|
|
|
|
case 'void':
|
|
|
|
case 'float':
|
|
|
|
case 'string':
|
|
|
|
case 'bool':
|
|
|
|
case 'callable':
|
|
|
|
case 'iterable':
|
|
|
|
case 'array':
|
|
|
|
case 'object':
|
|
|
|
case 'numeric':
|
|
|
|
case 'true':
|
|
|
|
case 'false':
|
|
|
|
case 'null':
|
|
|
|
case 'mixed':
|
|
|
|
return $type_string_lc;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ($type_string) {
|
|
|
|
case 'boolean':
|
2019-02-07 18:25:57 +01:00
|
|
|
return $php_version !== null ? $type_string : 'bool';
|
2018-01-10 06:07:47 +01:00
|
|
|
|
|
|
|
case 'integer':
|
2019-02-07 18:25:57 +01:00
|
|
|
return $php_version !== null ? $type_string : 'int';
|
2018-01-10 06:07:47 +01:00
|
|
|
|
|
|
|
case 'double':
|
|
|
|
case 'real':
|
2019-02-07 18:25:57 +01:00
|
|
|
return $php_version !== null ? $type_string : 'float';
|
2016-08-14 18:07:19 +02:00
|
|
|
}
|
2016-07-12 06:50:16 +02:00
|
|
|
|
|
|
|
return $type_string;
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
2018-01-10 06:07:47 +01:00
|
|
|
* @param ParseTree $parse_tree
|
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
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2019-12-27 18:49:28 +01:00
|
|
|
* @return Atomic|Union
|
2016-11-02 07:29:00 +01:00
|
|
|
*/
|
2018-07-07 06:06:05 +02:00
|
|
|
public static function getTypeFromTree(
|
|
|
|
ParseTree $parse_tree,
|
2019-02-07 18:25:57 +01:00
|
|
|
array $php_version = null,
|
2018-12-18 05:29:27 +01:00
|
|
|
array $template_type_map = []
|
2018-07-07 06:06:05 +02:00
|
|
|
) {
|
2018-03-21 01:19:26 +01:00
|
|
|
if ($parse_tree instanceof ParseTree\GenericTree) {
|
|
|
|
$generic_type = $parse_tree->value;
|
2016-06-14 07:23:57 +02:00
|
|
|
|
2019-12-27 18:49:28 +01:00
|
|
|
$generic_params = [];
|
2017-05-25 04:07:49 +02:00
|
|
|
|
2019-12-27 18:49:28 +01:00
|
|
|
foreach ($parse_tree->children as $i => $child_tree) {
|
|
|
|
$tree_type = self::getTypeFromTree($child_tree, null, $template_type_map);
|
|
|
|
|
|
|
|
if ($generic_type === 'class-string-map'
|
|
|
|
&& $i === 0
|
|
|
|
) {
|
|
|
|
if ($tree_type instanceof TTemplateParam) {
|
|
|
|
$template_type_map[$tree_type->param_name] = ['class-string-map' => [$tree_type->as]];
|
|
|
|
} elseif ($tree_type instanceof TNamedObject) {
|
|
|
|
$template_type_map[$tree_type->value] = ['class-string-map' => [self::getObject()]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$generic_params[] = $tree_type instanceof Union ? $tree_type : new Union([$tree_type]);
|
|
|
|
}
|
2016-06-14 07:23:57 +02:00
|
|
|
|
2019-02-07 18:25:57 +01:00
|
|
|
$generic_type_value = self::fixScalarTerms($generic_type);
|
2016-09-09 22:21:49 +02:00
|
|
|
|
2019-12-30 23:01:10 +01:00
|
|
|
if (($generic_type_value === 'array'
|
|
|
|
|| $generic_type_value === 'non-empty-array'
|
|
|
|
|| $generic_type_value === 'associative-array')
|
2019-02-27 15:08:27 +01:00
|
|
|
&& count($generic_params) === 1
|
|
|
|
) {
|
2019-01-05 06:15:53 +01:00
|
|
|
array_unshift($generic_params, new Union([new TArrayKey]));
|
2019-10-30 19:00:27 +01:00
|
|
|
} elseif (in_array($generic_type_value, ['iterable', 'Traversable', 'Iterator', 'IteratorAggregate'], true)
|
2018-12-02 20:59:08 +01:00
|
|
|
&& count($generic_params) === 1
|
2016-11-02 07:29:00 +01:00
|
|
|
) {
|
2017-01-15 01:06:58 +01:00
|
|
|
array_unshift($generic_params, new Union([new TMixed]));
|
2019-01-26 22:58:49 +01:00
|
|
|
} elseif ($generic_type_value === 'Generator') {
|
|
|
|
if (count($generic_params) === 1) {
|
|
|
|
array_unshift($generic_params, new Union([new TMixed]));
|
|
|
|
}
|
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
for ($i = 0, $l = 4 - count($generic_params); $i < $l; ++$i) {
|
2019-01-26 22:58:49 +01:00
|
|
|
$generic_params[] = new Union([new TMixed]);
|
|
|
|
}
|
2016-09-09 22:21:49 +02:00
|
|
|
}
|
|
|
|
|
2016-06-14 07:23:57 +02:00
|
|
|
if (!$generic_params) {
|
2019-01-08 20:50:45 +01:00
|
|
|
throw new TypeParseTreeException('No generic params provided for type');
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|
|
|
|
|
2019-12-30 23:01:10 +01:00
|
|
|
if ($generic_type_value === 'array' || $generic_type_value === 'associative-array') {
|
2017-01-15 01:06:58 +01:00
|
|
|
return new TArray($generic_params);
|
2016-09-22 04:15:46 +02:00
|
|
|
}
|
|
|
|
|
2019-02-27 15:08:27 +01:00
|
|
|
if ($generic_type_value === 'non-empty-array') {
|
|
|
|
return new Type\Atomic\TNonEmptyArray($generic_params);
|
|
|
|
}
|
|
|
|
|
2019-01-04 20:54:40 +01:00
|
|
|
if ($generic_type_value === 'iterable') {
|
2019-01-23 05:42:54 +01:00
|
|
|
return new TIterable($generic_params);
|
2019-01-04 20:54:40 +01:00
|
|
|
}
|
|
|
|
|
2019-10-09 00:44:46 +02:00
|
|
|
if ($generic_type_value === 'list') {
|
|
|
|
return new TList($generic_params[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($generic_type_value === 'non-empty-list') {
|
|
|
|
return new TNonEmptyList($generic_params[0]);
|
|
|
|
}
|
|
|
|
|
2019-01-02 15:00:45 +01:00
|
|
|
if ($generic_type_value === 'class-string') {
|
2019-01-02 20:24:31 +01:00
|
|
|
$class_name = (string) $generic_params[0];
|
|
|
|
|
|
|
|
if (isset($template_type_map[$class_name])) {
|
2019-03-22 20:59:10 +01:00
|
|
|
$first_class = array_keys($template_type_map[$class_name])[0];
|
2019-07-05 22:24:00 +02:00
|
|
|
|
2019-01-10 18:13:49 +01:00
|
|
|
return self::getGenericParamClass(
|
|
|
|
$class_name,
|
2019-03-22 20:59:10 +01:00
|
|
|
$template_type_map[$class_name][$first_class][0],
|
|
|
|
$first_class
|
2019-01-10 18:13:49 +01:00
|
|
|
);
|
2019-01-02 20:24:31 +01:00
|
|
|
}
|
|
|
|
|
2020-01-04 18:20:26 +01:00
|
|
|
$param_union_types = array_values($generic_params[0]->getAtomicTypes());
|
2019-01-20 04:45:58 +01:00
|
|
|
|
|
|
|
if (count($param_union_types) > 1) {
|
|
|
|
throw new TypeParseTreeException('Union types are not allowed in class string param');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$param_union_types[0] instanceof TNamedObject) {
|
|
|
|
throw new TypeParseTreeException('Class string param should be a named object');
|
|
|
|
}
|
|
|
|
|
|
|
|
return new TClassString($class_name, $param_union_types[0]);
|
2019-01-02 15:00:45 +01:00
|
|
|
}
|
|
|
|
|
2019-12-27 18:49:28 +01:00
|
|
|
if ($generic_type_value === 'class-string-map') {
|
|
|
|
if (count($generic_params) !== 2) {
|
|
|
|
throw new TypeParseTreeException(
|
|
|
|
'There should only be two params for class-string-map, '
|
|
|
|
. count($generic_params) . ' provided'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-01-04 18:20:26 +01:00
|
|
|
$template_marker_parts = array_values($generic_params[0]->getAtomicTypes());
|
2019-12-27 18:49:28 +01:00
|
|
|
|
|
|
|
$template_marker = $template_marker_parts[0];
|
|
|
|
|
|
|
|
$template_as_type = null;
|
|
|
|
|
|
|
|
if ($template_marker instanceof TNamedObject) {
|
|
|
|
$template_param_name = $template_marker->value;
|
|
|
|
} elseif ($template_marker instanceof Atomic\TTemplateParam) {
|
|
|
|
$template_param_name = $template_marker->param_name;
|
2020-01-04 18:20:26 +01:00
|
|
|
$template_as_type = array_values($template_marker->as->getAtomicTypes())[0];
|
2019-12-27 18:49:28 +01:00
|
|
|
|
|
|
|
if (!$template_as_type instanceof TNamedObject) {
|
|
|
|
throw new TypeParseTreeException(
|
|
|
|
'Unrecognised as type'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new TypeParseTreeException(
|
|
|
|
'Unrecognised class-string-map templated param'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new TClassStringMap(
|
|
|
|
$template_param_name,
|
|
|
|
$template_as_type,
|
|
|
|
$generic_params[1]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-24 04:27:36 +02:00
|
|
|
if ($generic_type_value === 'key-of') {
|
|
|
|
$param_name = (string) $generic_params[0];
|
|
|
|
|
|
|
|
if (isset($template_type_map[$param_name])) {
|
|
|
|
$defining_class = array_keys($template_type_map[$param_name])[0];
|
|
|
|
|
|
|
|
return new Atomic\TTemplateKeyOf(
|
|
|
|
$param_name,
|
|
|
|
$defining_class
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-01-04 18:20:26 +01:00
|
|
|
$param_union_types = array_values($generic_params[0]->getAtomicTypes());
|
2019-05-24 04:27:36 +02:00
|
|
|
|
|
|
|
if (count($param_union_types) > 1) {
|
|
|
|
throw new TypeParseTreeException('Union types are not allowed in key-of type');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$param_union_types[0] instanceof Atomic\TScalarClassConstant) {
|
2019-05-24 08:12:58 +02:00
|
|
|
throw new TypeParseTreeException(
|
|
|
|
'Untemplated key-of param ' . $param_name . ' should be a class constant'
|
|
|
|
);
|
2019-05-24 04:27:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return new Atomic\TKeyOfClassConstant(
|
|
|
|
$param_union_types[0]->fq_classlike_name,
|
|
|
|
$param_union_types[0]->const_name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-28 16:44:04 +02:00
|
|
|
if ($generic_type_value === 'value-of') {
|
|
|
|
$param_name = (string) $generic_params[0];
|
|
|
|
|
|
|
|
if (isset($template_type_map[$param_name])) {
|
|
|
|
$defining_class = array_keys($template_type_map[$param_name])[0];
|
|
|
|
|
|
|
|
return new Atomic\TTemplateKeyOf(
|
|
|
|
$param_name,
|
|
|
|
$defining_class
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-01-04 18:20:26 +01:00
|
|
|
$param_union_types = array_values($generic_params[0]->getAtomicTypes());
|
2019-05-28 16:44:04 +02:00
|
|
|
|
|
|
|
if (count($param_union_types) > 1) {
|
|
|
|
throw new TypeParseTreeException('Union types are not allowed in value-of type');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$param_union_types[0] instanceof Atomic\TScalarClassConstant) {
|
|
|
|
throw new TypeParseTreeException(
|
|
|
|
'Untemplated value-of param ' . $param_name . ' should be a class constant'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Atomic\TValueOfClassConstant(
|
|
|
|
$param_union_types[0]->fq_classlike_name,
|
|
|
|
$param_union_types[0]->const_name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-02-05 14:02:06 +01:00
|
|
|
if (isset(self::PSALM_RESERVED_WORDS[$generic_type_value])
|
|
|
|
&& $generic_type_value !== 'self'
|
|
|
|
&& $generic_type_value !== 'static'
|
|
|
|
) {
|
2019-01-29 16:34:31 +01:00
|
|
|
throw new TypeParseTreeException('Cannot create generic object with reserved word');
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:06:58 +01:00
|
|
|
return new TGenericObject($generic_type_value, $generic_params);
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
if ($parse_tree instanceof ParseTree\UnionTree) {
|
2018-06-09 05:54:07 +02:00
|
|
|
$has_null = false;
|
|
|
|
|
|
|
|
$atomic_types = [];
|
|
|
|
|
|
|
|
foreach ($parse_tree->children as $child_tree) {
|
|
|
|
if ($child_tree instanceof ParseTree\NullableTree) {
|
2019-02-07 18:25:57 +01:00
|
|
|
$atomic_type = self::getTypeFromTree($child_tree->children[0], null, $template_type_map);
|
2018-06-09 05:54:07 +02:00
|
|
|
$has_null = true;
|
|
|
|
} else {
|
2019-02-07 18:25:57 +01:00
|
|
|
$atomic_type = self::getTypeFromTree($child_tree, null, $template_type_map);
|
2018-06-09 05:54:07 +02:00
|
|
|
}
|
2016-12-07 20:13:39 +01:00
|
|
|
|
2018-06-30 19:09:05 +02:00
|
|
|
if ($atomic_type instanceof Union) {
|
2020-01-04 18:20:26 +01:00
|
|
|
foreach ($atomic_type->getAtomicTypes() as $type) {
|
2018-06-30 19:09:05 +02:00
|
|
|
$atomic_types[] = $type;
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-06-09 05:54:07 +02:00
|
|
|
$atomic_types[] = $atomic_type;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($has_null) {
|
|
|
|
$atomic_types[] = new TNull;
|
2020-01-30 05:41:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!$atomic_types) {
|
|
|
|
throw new TypeParseTreeException(
|
|
|
|
'No atomic types found'
|
|
|
|
);
|
2018-06-09 05:54:07 +02:00
|
|
|
}
|
2016-06-14 07:23:57 +02:00
|
|
|
|
2018-06-09 05:54:07 +02:00
|
|
|
return TypeCombination::combineTypes($atomic_types);
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
if ($parse_tree instanceof ParseTree\IntersectionTree) {
|
2018-03-18 18:57:04 +01:00
|
|
|
$intersection_types = array_map(
|
|
|
|
/**
|
|
|
|
* @return Atomic
|
|
|
|
*/
|
2018-12-18 05:29:27 +01:00
|
|
|
function (ParseTree $child_tree) use ($template_type_map) {
|
2019-02-07 18:25:57 +01:00
|
|
|
$atomic_type = self::getTypeFromTree($child_tree, null, $template_type_map);
|
2018-03-18 18:57:04 +01:00
|
|
|
|
|
|
|
if (!$atomic_type instanceof Atomic) {
|
2018-03-23 03:28:06 +01:00
|
|
|
throw new TypeParseTreeException(
|
|
|
|
'Intersection types cannot contain unions'
|
2018-03-18 18:57:04 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $atomic_type;
|
|
|
|
},
|
|
|
|
$parse_tree->children
|
|
|
|
);
|
|
|
|
|
2019-05-28 06:32:17 +02:00
|
|
|
$keyed_intersection_types = [];
|
|
|
|
|
2018-03-18 18:57:04 +01:00
|
|
|
foreach ($intersection_types as $intersection_type) {
|
2019-06-19 18:00:07 +02:00
|
|
|
if (!$intersection_type instanceof TIterable
|
|
|
|
&& !$intersection_type instanceof TNamedObject
|
2019-02-22 03:40:06 +01:00
|
|
|
&& !$intersection_type instanceof TTemplateParam
|
2019-06-19 18:00:07 +02:00
|
|
|
&& !$intersection_type instanceof TObjectWithProperties
|
2018-11-02 18:08:56 +01:00
|
|
|
) {
|
2019-01-04 20:54:40 +01:00
|
|
|
throw new TypeParseTreeException(
|
|
|
|
'Intersection types must all be objects, ' . get_class($intersection_type) . ' provided'
|
|
|
|
);
|
2018-03-18 18:57:04 +01:00
|
|
|
}
|
2019-05-28 06:32:17 +02:00
|
|
|
|
2019-05-30 01:58:54 +02:00
|
|
|
$keyed_intersection_types[
|
|
|
|
$intersection_type instanceof TIterable
|
|
|
|
? $intersection_type->getId()
|
|
|
|
: $intersection_type->getKey()
|
|
|
|
] = $intersection_type;
|
2018-03-18 18:57:04 +01:00
|
|
|
}
|
|
|
|
|
2019-05-28 06:32:17 +02:00
|
|
|
$first_type = array_shift($keyed_intersection_types);
|
2018-03-18 18:57:04 +01:00
|
|
|
|
2019-05-28 06:32:17 +02:00
|
|
|
$first_type->extra_types = $keyed_intersection_types;
|
2018-03-18 18:57:04 +01:00
|
|
|
|
2018-03-22 22:55:36 +01:00
|
|
|
return $first_type;
|
2018-03-18 18:57:04 +01:00
|
|
|
}
|
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
if ($parse_tree instanceof ParseTree\ObjectLikeTree) {
|
2016-09-22 06:31:07 +02:00
|
|
|
$properties = [];
|
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
$type = $parse_tree->value;
|
2016-09-22 06:31:07 +02:00
|
|
|
|
2017-11-17 07:18:13 +01:00
|
|
|
foreach ($parse_tree->children as $i => $property_branch) {
|
2018-03-21 01:19:26 +01:00
|
|
|
if (!$property_branch instanceof ParseTree\ObjectLikePropertyTree) {
|
2019-02-07 18:25:57 +01:00
|
|
|
$property_type = self::getTypeFromTree($property_branch, null, $template_type_map);
|
2018-03-17 04:37:10 +01:00
|
|
|
$property_maybe_undefined = false;
|
2017-11-17 07:18:13 +01:00
|
|
|
$property_key = (string)$i;
|
2018-03-21 01:19:26 +01:00
|
|
|
} elseif (count($property_branch->children) === 1) {
|
2019-02-07 18:25:57 +01:00
|
|
|
$property_type = self::getTypeFromTree($property_branch->children[0], null, $template_type_map);
|
2018-03-17 04:37:10 +01:00
|
|
|
$property_maybe_undefined = $property_branch->possibly_undefined;
|
2018-03-21 01:19:26 +01:00
|
|
|
$property_key = $property_branch->value;
|
2017-11-17 07:18:13 +01:00
|
|
|
} else {
|
2019-02-08 00:10:32 +01:00
|
|
|
throw new TypeParseTreeException(
|
|
|
|
'Missing property type'
|
2018-03-21 01:19:26 +01:00
|
|
|
);
|
2017-11-17 07:18:13 +01:00
|
|
|
}
|
|
|
|
|
2016-10-03 00:59:16 +02:00
|
|
|
if (!$property_type instanceof Union) {
|
|
|
|
$property_type = new Union([$property_type]);
|
|
|
|
}
|
2018-03-17 04:37:10 +01:00
|
|
|
|
|
|
|
if ($property_maybe_undefined) {
|
|
|
|
$property_type->possibly_undefined = true;
|
|
|
|
}
|
|
|
|
|
2017-11-17 07:18:13 +01:00
|
|
|
$properties[$property_key] = $property_type;
|
2016-09-22 06:31:07 +02:00
|
|
|
}
|
|
|
|
|
2019-01-18 06:56:24 +01:00
|
|
|
if ($type !== 'array' && $type !== 'object') {
|
2018-09-06 06:41:07 +02:00
|
|
|
throw new TypeParseTreeException('Unexpected brace character');
|
2016-10-30 17:46:18 +01:00
|
|
|
}
|
|
|
|
|
2018-01-19 22:06:30 +01:00
|
|
|
if (!$properties) {
|
2019-01-08 20:50:45 +01:00
|
|
|
throw new TypeParseTreeException('No properties supplied for ObjectLike');
|
2018-01-19 22:06:30 +01:00
|
|
|
}
|
|
|
|
|
2019-01-18 06:56:24 +01:00
|
|
|
if ($type === 'object') {
|
|
|
|
return new TObjectWithProperties($properties);
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:06:58 +01:00
|
|
|
return new ObjectLike($properties);
|
2016-09-22 06:31:07 +02:00
|
|
|
}
|
|
|
|
|
2018-03-27 04:13:10 +02:00
|
|
|
if ($parse_tree instanceof ParseTree\CallableWithReturnTypeTree) {
|
2019-02-07 18:25:57 +01:00
|
|
|
$callable_type = self::getTypeFromTree($parse_tree->children[0], null, $template_type_map);
|
2018-03-27 04:13:10 +02:00
|
|
|
|
2019-06-03 12:19:52 +02:00
|
|
|
if (!$callable_type instanceof TCallable && !$callable_type instanceof Type\Atomic\TFn) {
|
2018-03-27 04:13:10 +02:00
|
|
|
throw new \InvalidArgumentException('Parsing callable tree node should return TCallable');
|
|
|
|
}
|
|
|
|
|
2018-04-16 00:16:31 +02:00
|
|
|
if (!isset($parse_tree->children[1])) {
|
2018-06-09 05:54:07 +02:00
|
|
|
throw new TypeParseTreeException('Invalid return type');
|
2018-04-16 00:16:31 +02:00
|
|
|
}
|
|
|
|
|
2019-02-07 18:25:57 +01:00
|
|
|
$return_type = self::getTypeFromTree($parse_tree->children[1], null, $template_type_map);
|
2018-03-27 04:13:10 +02:00
|
|
|
|
|
|
|
$callable_type->return_type = $return_type instanceof Union ? $return_type : new Union([$return_type]);
|
|
|
|
|
|
|
|
return $callable_type;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($parse_tree instanceof ParseTree\CallableTree) {
|
|
|
|
$params = array_map(
|
|
|
|
/**
|
|
|
|
* @return FunctionLikeParameter
|
|
|
|
*/
|
2018-12-18 05:29:27 +01:00
|
|
|
function (ParseTree $child_tree) use ($template_type_map) {
|
2018-03-27 04:13:10 +02:00
|
|
|
$is_variadic = false;
|
|
|
|
$is_optional = false;
|
|
|
|
|
|
|
|
if ($child_tree instanceof ParseTree\CallableParamTree) {
|
2019-02-07 18:25:57 +01:00
|
|
|
$tree_type = self::getTypeFromTree($child_tree->children[0], null, $template_type_map);
|
2018-03-27 04:13:10 +02:00
|
|
|
$is_variadic = $child_tree->variadic;
|
|
|
|
$is_optional = $child_tree->has_default;
|
|
|
|
} else {
|
2019-02-21 22:00:18 +01:00
|
|
|
if ($child_tree instanceof ParseTree\Value && strpos($child_tree->value, '$') > 0) {
|
|
|
|
$child_tree->value = preg_replace('/(.+)\$.*/', '$1', $child_tree->value);
|
|
|
|
}
|
|
|
|
|
2019-02-07 18:25:57 +01:00
|
|
|
$tree_type = self::getTypeFromTree($child_tree, null, $template_type_map);
|
2018-03-27 04:13:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$tree_type = $tree_type instanceof Union ? $tree_type : new Union([$tree_type]);
|
|
|
|
|
2019-05-21 17:51:41 +02:00
|
|
|
$param = new FunctionLikeParameter(
|
2018-03-27 04:13:10 +02:00
|
|
|
'',
|
|
|
|
false,
|
|
|
|
$tree_type,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
$is_optional,
|
|
|
|
false,
|
|
|
|
$is_variadic
|
|
|
|
);
|
2019-05-21 17:51:41 +02:00
|
|
|
|
|
|
|
// type is not authoratative
|
|
|
|
$param->signature_type = null;
|
|
|
|
|
|
|
|
return $param;
|
2018-03-27 04:13:10 +02:00
|
|
|
},
|
|
|
|
$parse_tree->children
|
|
|
|
);
|
|
|
|
|
2018-04-08 16:02:41 +02:00
|
|
|
if (in_array(strtolower($parse_tree->value), ['closure', '\closure'], true)) {
|
2019-06-03 12:19:52 +02:00
|
|
|
return new Type\Atomic\TFn('Closure', $params);
|
2018-03-27 04:13:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return new TCallable($parse_tree->value, $params);
|
|
|
|
}
|
|
|
|
|
2018-03-23 03:28:06 +01:00
|
|
|
if ($parse_tree instanceof ParseTree\EncapsulationTree) {
|
2019-02-07 18:25:57 +01:00
|
|
|
return self::getTypeFromTree($parse_tree->children[0], null, $template_type_map);
|
2018-03-23 03:28:06 +01:00
|
|
|
}
|
|
|
|
|
2018-06-09 05:54:07 +02:00
|
|
|
if ($parse_tree instanceof ParseTree\NullableTree) {
|
2019-01-13 00:18:23 +01:00
|
|
|
if (!isset($parse_tree->children[0])) {
|
|
|
|
throw new TypeParseTreeException('Misplaced question mark');
|
|
|
|
}
|
|
|
|
|
2019-02-07 18:25:57 +01:00
|
|
|
$non_nullable_type = self::getTypeFromTree($parse_tree->children[0], null, $template_type_map);
|
2018-06-09 05:54:07 +02:00
|
|
|
|
2018-08-29 22:03:16 +02:00
|
|
|
if ($non_nullable_type instanceof Union) {
|
|
|
|
$non_nullable_type->addType(new TNull);
|
2019-07-05 22:24:00 +02:00
|
|
|
|
2018-08-29 22:03:16 +02:00
|
|
|
return $non_nullable_type;
|
2018-06-09 05:54:07 +02:00
|
|
|
}
|
|
|
|
|
2019-12-27 18:49:28 +01:00
|
|
|
return TypeCombination::combineTypes([
|
|
|
|
new TNull,
|
|
|
|
$non_nullable_type,
|
|
|
|
]);
|
2018-06-09 05:54:07 +02:00
|
|
|
}
|
|
|
|
|
2018-09-06 04:40:52 +02:00
|
|
|
if ($parse_tree instanceof ParseTree\MethodTree
|
|
|
|
|| $parse_tree instanceof ParseTree\MethodWithReturnTypeTree
|
|
|
|
) {
|
2018-09-06 04:36:32 +02:00
|
|
|
throw new TypeParseTreeException('Misplaced brackets');
|
|
|
|
}
|
|
|
|
|
2019-05-24 04:27:36 +02:00
|
|
|
if ($parse_tree instanceof ParseTree\IndexedAccessTree) {
|
|
|
|
if (!isset($parse_tree->children[0]) || !$parse_tree->children[0] instanceof ParseTree\Value) {
|
|
|
|
throw new TypeParseTreeException('Unrecognised indexed access');
|
|
|
|
}
|
|
|
|
|
|
|
|
$offset_param_name = $parse_tree->value;
|
|
|
|
$array_param_name = $parse_tree->children[0]->value;
|
|
|
|
|
|
|
|
if (!isset($template_type_map[$offset_param_name])) {
|
|
|
|
throw new TypeParseTreeException('Unrecognised template param ' . $offset_param_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($template_type_map[$array_param_name])) {
|
|
|
|
throw new TypeParseTreeException('Unrecognised template param ' . $array_param_name);
|
|
|
|
}
|
|
|
|
|
2019-05-24 08:12:58 +02:00
|
|
|
$offset_template_data = $template_type_map[$offset_param_name];
|
|
|
|
|
|
|
|
$offset_defining_class = array_keys($offset_template_data)[0];
|
|
|
|
|
2019-09-18 20:21:06 +02:00
|
|
|
if (!$offset_defining_class
|
|
|
|
&& isset($offset_template_data[''])
|
|
|
|
&& $offset_template_data[''][0]->isSingle()
|
|
|
|
) {
|
2020-01-04 18:20:26 +01:00
|
|
|
$offset_template_type = array_values($offset_template_data[''][0]->getAtomicTypes())[0];
|
2019-05-24 08:12:58 +02:00
|
|
|
|
|
|
|
if ($offset_template_type instanceof Type\Atomic\TTemplateKeyOf) {
|
|
|
|
$offset_defining_class = (string) $offset_template_type->defining_class;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-24 04:27:36 +02:00
|
|
|
$array_defining_class = array_keys($template_type_map[$array_param_name])[0];
|
|
|
|
|
2019-12-29 00:37:55 +01:00
|
|
|
if ($offset_defining_class !== $array_defining_class
|
|
|
|
&& substr($offset_defining_class, 0, 3) !== 'fn-'
|
|
|
|
) {
|
2019-05-24 04:27:36 +02:00
|
|
|
throw new TypeParseTreeException('Template params are defined in different locations');
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Atomic\TTemplateIndexedAccess(
|
|
|
|
$array_param_name,
|
|
|
|
$offset_param_name,
|
2019-05-24 08:12:58 +02:00
|
|
|
$array_defining_class
|
2019-05-24 04:27:36 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-12-27 18:49:28 +01:00
|
|
|
if ($parse_tree instanceof ParseTree\TemplateAsTree) {
|
|
|
|
return new Atomic\TTemplateParam(
|
|
|
|
$parse_tree->param_name,
|
|
|
|
new Union([new TNamedObject($parse_tree->as)]),
|
|
|
|
'class-string-map'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
if (!$parse_tree instanceof ParseTree\Value) {
|
2018-03-27 04:13:10 +02:00
|
|
|
throw new \InvalidArgumentException('Unrecognised parse tree type ' . get_class($parse_tree));
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
|
|
|
|
2018-05-20 23:19:53 +02:00
|
|
|
if ($parse_tree->value[0] === '"' || $parse_tree->value[0] === '\'') {
|
|
|
|
return new TLiteralString(substr($parse_tree->value, 1, -1));
|
|
|
|
}
|
|
|
|
|
2018-05-21 06:46:56 +02:00
|
|
|
if (strpos($parse_tree->value, '::')) {
|
|
|
|
list($fq_classlike_name, $const_name) = explode('::', $parse_tree->value);
|
2018-12-13 06:09:01 +01:00
|
|
|
|
2018-12-18 05:29:27 +01:00
|
|
|
if (isset($template_type_map[$fq_classlike_name]) && $const_name === 'class') {
|
2019-03-22 20:59:10 +01:00
|
|
|
$first_class = array_keys($template_type_map[$fq_classlike_name])[0];
|
|
|
|
|
2019-01-10 18:13:49 +01:00
|
|
|
return self::getGenericParamClass(
|
|
|
|
$fq_classlike_name,
|
2019-03-22 20:59:10 +01:00
|
|
|
$template_type_map[$fq_classlike_name][$first_class][0],
|
|
|
|
$first_class
|
2019-01-10 18:13:49 +01:00
|
|
|
);
|
2018-12-13 06:09:01 +01:00
|
|
|
}
|
|
|
|
|
2018-12-22 18:12:35 +01:00
|
|
|
if ($const_name === 'class') {
|
|
|
|
return new Atomic\TLiteralClassString($fq_classlike_name);
|
|
|
|
}
|
|
|
|
|
2018-05-21 06:46:56 +02:00
|
|
|
return new Atomic\TScalarClassConstant($fq_classlike_name, $const_name);
|
|
|
|
}
|
|
|
|
|
2019-01-28 00:34:13 +01:00
|
|
|
if (preg_match('/^\-?(0|[1-9][0-9]*)(\.[0-9]{1,})$/', $parse_tree->value)) {
|
|
|
|
return new TLiteralFloat((float) $parse_tree->value);
|
|
|
|
}
|
|
|
|
|
2018-05-20 23:19:53 +02:00
|
|
|
if (preg_match('/^\-?(0|[1-9][0-9]*)$/', $parse_tree->value)) {
|
|
|
|
return new TLiteralInt((int) $parse_tree->value);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!preg_match('@^(\$this|\\\\?[a-zA-Z_\x7f-\xff][\\\\\-0-9a-zA-Z_\x7f-\xff]*)$@', $parse_tree->value)) {
|
|
|
|
throw new TypeParseTreeException('Invalid type \'' . $parse_tree->value . '\'');
|
|
|
|
}
|
|
|
|
|
2019-06-23 06:19:41 +02:00
|
|
|
$atomic_type_string = self::fixScalarTerms($parse_tree->value, $php_version);
|
|
|
|
|
|
|
|
$atomic_type = Atomic::create($atomic_type_string, $php_version, $template_type_map);
|
|
|
|
|
2019-06-23 14:47:49 +02:00
|
|
|
$atomic_type->offset_start = $parse_tree->offset_start;
|
|
|
|
$atomic_type->offset_end = $parse_tree->offset_end;
|
2016-09-09 22:21:49 +02:00
|
|
|
|
2019-06-23 06:19:41 +02:00
|
|
|
return $atomic_type;
|
2016-07-12 06:50:16 +02:00
|
|
|
}
|
|
|
|
|
2019-01-10 18:13:49 +01:00
|
|
|
private static function getGenericParamClass(
|
|
|
|
string $param_name,
|
|
|
|
Union $as,
|
2019-12-29 00:37:55 +01:00
|
|
|
string $defining_class
|
2019-02-22 03:40:06 +01:00
|
|
|
) : Atomic\TTemplateParamClass {
|
2019-01-05 06:15:53 +01:00
|
|
|
if ($as->hasMixed()) {
|
2019-02-22 03:40:06 +01:00
|
|
|
return new Atomic\TTemplateParamClass(
|
2019-01-05 06:15:53 +01:00
|
|
|
$param_name,
|
2019-01-10 18:13:49 +01:00
|
|
|
'object',
|
|
|
|
null,
|
|
|
|
$defining_class
|
2019-01-05 06:15:53 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$as->isSingle()) {
|
|
|
|
throw new TypeParseTreeException(
|
|
|
|
'Invalid templated classname \'' . $as . '\''
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-01-04 18:20:26 +01:00
|
|
|
foreach ($as->getAtomicTypes() as $t) {
|
2019-01-05 06:15:53 +01:00
|
|
|
if ($t instanceof TObject) {
|
2019-02-22 03:40:06 +01:00
|
|
|
return new Atomic\TTemplateParamClass(
|
2019-01-10 18:13:49 +01:00
|
|
|
$param_name,
|
|
|
|
'object',
|
|
|
|
null,
|
|
|
|
$defining_class
|
2019-01-05 06:15:53 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-01-05 16:32:39 +01:00
|
|
|
if ($t instanceof TIterable) {
|
2019-01-23 05:42:54 +01:00
|
|
|
$traversable = new TGenericObject(
|
|
|
|
'Traversable',
|
|
|
|
$t->type_params
|
|
|
|
);
|
2019-01-05 16:32:39 +01:00
|
|
|
|
|
|
|
$as->substitute(new Union([$t]), new Union([$traversable]));
|
2019-07-05 22:24:00 +02:00
|
|
|
|
2019-02-22 03:40:06 +01:00
|
|
|
return new Atomic\TTemplateParamClass(
|
2019-01-05 16:32:39 +01:00
|
|
|
$param_name,
|
|
|
|
$traversable->value,
|
2019-01-20 04:45:58 +01:00
|
|
|
$traversable,
|
2019-01-10 18:13:49 +01:00
|
|
|
$defining_class
|
2019-01-05 16:32:39 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-02 16:51:18 +01:00
|
|
|
if ($t instanceof Atomic\TTemplateParam) {
|
|
|
|
$t_atomic_types = $t->as->getAtomicTypes();
|
2020-02-02 17:04:29 +01:00
|
|
|
$t_atomic_type = \count($t_atomic_types) === 1 ? \reset($t_atomic_types) : null;
|
2020-02-02 16:51:18 +01:00
|
|
|
|
|
|
|
if (!$t_atomic_type instanceof TNamedObject) {
|
|
|
|
$t_atomic_type = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Atomic\TTemplateParamClass(
|
|
|
|
$t->param_name,
|
|
|
|
$t_atomic_type ? $t_atomic_type->value : 'object',
|
|
|
|
$t_atomic_type,
|
|
|
|
$t->defining_class
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-01-05 06:15:53 +01:00
|
|
|
if (!$t instanceof TNamedObject) {
|
|
|
|
throw new TypeParseTreeException(
|
2020-02-02 16:51:18 +01:00
|
|
|
'Invalid templated classname \'' . $t->getId() . '\''
|
2019-01-05 06:15:53 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-02-22 03:40:06 +01:00
|
|
|
return new Atomic\TTemplateParamClass(
|
2019-01-05 06:15:53 +01:00
|
|
|
$param_name,
|
2019-01-05 16:32:39 +01:00
|
|
|
$t->value,
|
2019-01-20 04:45:58 +01:00
|
|
|
$t,
|
2019-01-10 18:13:49 +01:00
|
|
|
$defining_class
|
2019-01-05 06:15:53 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new \LogicException('Should never get here');
|
|
|
|
}
|
|
|
|
|
2016-07-12 06:50:16 +02:00
|
|
|
/**
|
2018-05-20 23:19:53 +02:00
|
|
|
* @param string $string_type
|
2018-01-02 03:17:23 +01:00
|
|
|
* @param bool $ignore_space
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2019-11-21 17:03:18 +01:00
|
|
|
* @return list<array{0: string, 1: int}>
|
2016-07-12 06:50:16 +02:00
|
|
|
*/
|
2018-05-20 23:19:53 +02:00
|
|
|
public static function tokenize($string_type, $ignore_space = true)
|
2016-07-12 06:50:16 +02:00
|
|
|
{
|
2019-06-23 05:30:40 +02:00
|
|
|
$type_tokens = [['', 0]];
|
2018-05-20 23:19:53 +02:00
|
|
|
$was_char = false;
|
|
|
|
$quote_char = null;
|
|
|
|
$escaped = false;
|
2016-07-12 06:50:16 +02:00
|
|
|
|
2018-05-20 23:19:53 +02:00
|
|
|
if (isset(self::$memoized_tokens[$string_type])) {
|
|
|
|
return self::$memoized_tokens[$string_type];
|
2018-02-04 18:23:32 +01:00
|
|
|
}
|
|
|
|
|
2018-01-21 03:22:33 +01:00
|
|
|
// index of last type token
|
|
|
|
$rtc = 0;
|
|
|
|
|
2018-05-20 23:19:53 +02:00
|
|
|
$chars = str_split($string_type);
|
2019-08-08 23:25:56 +02:00
|
|
|
$was_space = false;
|
|
|
|
|
2018-03-27 04:13:10 +02:00
|
|
|
for ($i = 0, $c = count($chars); $i < $c; ++$i) {
|
|
|
|
$char = $chars[$i];
|
|
|
|
|
2018-05-20 23:19:53 +02:00
|
|
|
if (!$quote_char && $char === ' ' && $ignore_space) {
|
2019-08-08 23:25:56 +02:00
|
|
|
$was_space = true;
|
2018-05-20 23:19:53 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-12-19 01:28:32 +01:00
|
|
|
if ($was_space
|
|
|
|
&& ($char === '$'
|
|
|
|
|| ($char === '.'
|
|
|
|
&& ($chars[$i + 1] ?? null) === '.'
|
|
|
|
&& ($chars[$i + 2] ?? null) === '.'
|
|
|
|
&& ($chars[$i + 3] ?? null) === '$'))
|
|
|
|
) {
|
2019-08-08 23:25:56 +02:00
|
|
|
$type_tokens[++$rtc] = [' ', $i - 1];
|
|
|
|
$type_tokens[++$rtc] = ['', $i];
|
2019-12-27 18:49:28 +01:00
|
|
|
} elseif ($was_space
|
|
|
|
&& $char === 'a'
|
|
|
|
&& ($chars[$i + 1] ?? null) === 's'
|
|
|
|
&& ($chars[$i + 2] ?? null) === ' '
|
|
|
|
) {
|
|
|
|
$type_tokens[++$rtc] = ['as', $i - 1];
|
|
|
|
$type_tokens[++$rtc] = ['', ++$i];
|
|
|
|
continue;
|
2019-08-08 23:25:56 +02:00
|
|
|
} elseif ($was_char) {
|
2019-06-23 05:30:40 +02:00
|
|
|
$type_tokens[++$rtc] = ['', $i];
|
2018-05-20 23:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($quote_char) {
|
2019-12-11 16:52:46 +01:00
|
|
|
if ($char === $quote_char && $i > 0 && !$escaped) {
|
2018-05-20 23:19:53 +02:00
|
|
|
$quote_char = null;
|
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
$type_tokens[$rtc][0] .= $char;
|
2018-05-20 23:19:53 +02:00
|
|
|
$was_char = true;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$was_char = false;
|
|
|
|
|
|
|
|
if ($char === '\\'
|
|
|
|
&& !$escaped
|
|
|
|
&& $i < $c - 1
|
|
|
|
&& ($chars[$i + 1] === $quote_char || $chars[$i + 1] === '\\')
|
|
|
|
) {
|
|
|
|
$escaped = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$escaped = false;
|
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
$type_tokens[$rtc][0] .= $char;
|
2018-05-20 23:19:53 +02:00
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($char === '"' || $char === '\'') {
|
2019-06-23 05:30:40 +02:00
|
|
|
if ($type_tokens[$rtc][0] === '') {
|
|
|
|
$type_tokens[$rtc] = [$char, $i];
|
2018-05-20 23:19:53 +02:00
|
|
|
} else {
|
2019-06-23 05:30:40 +02:00
|
|
|
$type_tokens[++$rtc] = [$char, $i];
|
2018-05-20 23:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$quote_char = $char;
|
|
|
|
|
|
|
|
$was_char = false;
|
2019-08-08 23:25:56 +02:00
|
|
|
$was_space = false;
|
|
|
|
|
2018-05-20 23:19:53 +02:00
|
|
|
continue;
|
2016-07-12 06:50:16 +02:00
|
|
|
}
|
|
|
|
|
2018-03-27 04:13:10 +02:00
|
|
|
if ($char === '<'
|
|
|
|
|| $char === '>'
|
|
|
|
|| $char === '|'
|
|
|
|
|| $char === '?'
|
|
|
|
|| $char === ','
|
|
|
|
|| $char === '{'
|
|
|
|
|| $char === '}'
|
|
|
|
|| $char === '['
|
|
|
|
|| $char === ']'
|
|
|
|
|| $char === '('
|
|
|
|
|| $char === ')'
|
|
|
|
|| $char === ' '
|
|
|
|
|| $char === '&'
|
|
|
|
|| $char === '='
|
2016-11-02 07:29:00 +01:00
|
|
|
) {
|
2019-06-23 05:30:40 +02:00
|
|
|
if ($type_tokens[$rtc][0] === '') {
|
|
|
|
$type_tokens[$rtc] = [$char, $i];
|
2016-11-02 07:29:00 +01:00
|
|
|
} else {
|
2019-06-23 05:30:40 +02:00
|
|
|
$type_tokens[++$rtc] = [$char, $i];
|
2016-07-12 06:50:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$was_char = true;
|
2019-08-08 23:25:56 +02:00
|
|
|
$was_space = false;
|
2018-05-20 23:19:53 +02:00
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-05-21 06:46:56 +02:00
|
|
|
if ($char === ':') {
|
|
|
|
if ($i + 1 < $c && $chars[$i + 1] === ':') {
|
2019-06-23 05:30:40 +02:00
|
|
|
if ($type_tokens[$rtc][0] === '') {
|
|
|
|
$type_tokens[$rtc] = ['::', $i];
|
2018-05-21 06:46:56 +02:00
|
|
|
} else {
|
2019-06-23 05:30:40 +02:00
|
|
|
$type_tokens[++$rtc] = ['::', $i];
|
2018-05-21 06:46:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$was_char = true;
|
2019-08-08 23:25:56 +02:00
|
|
|
$was_space = false;
|
2018-05-21 06:46:56 +02:00
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
++$i;
|
2018-05-21 06:46:56 +02:00
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
if ($type_tokens[$rtc][0] === '') {
|
|
|
|
$type_tokens[$rtc] = [':', $i];
|
2018-05-21 06:46:56 +02:00
|
|
|
} else {
|
2019-06-23 05:30:40 +02:00
|
|
|
$type_tokens[++$rtc] = [':', $i];
|
2018-05-21 06:46:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$was_char = true;
|
2019-08-08 23:25:56 +02:00
|
|
|
$was_space = false;
|
2018-05-21 06:46:56 +02:00
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-05-20 23:19:53 +02:00
|
|
|
if ($char === '.') {
|
2019-01-28 00:34:13 +01:00
|
|
|
if ($i + 1 < $c
|
|
|
|
&& is_numeric($chars[$i + 1])
|
|
|
|
&& $i > 0
|
|
|
|
&& is_numeric($chars[$i - 1])
|
|
|
|
) {
|
2019-06-23 05:30:40 +02:00
|
|
|
$type_tokens[$rtc][0] .= $char;
|
2019-01-28 00:34:13 +01:00
|
|
|
$was_char = false;
|
2019-08-08 23:25:56 +02:00
|
|
|
$was_space = false;
|
2019-01-28 00:34:13 +01:00
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-03-27 20:43:39 +02:00
|
|
|
if ($i + 2 > $c || $chars[$i + 1] !== '.' || $chars[$i + 2] !== '.') {
|
2018-03-27 04:13:10 +02:00
|
|
|
throw new TypeParseTreeException('Unexpected token ' . $char);
|
|
|
|
}
|
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
if ($type_tokens[$rtc][0] === '') {
|
|
|
|
$type_tokens[$rtc] = ['...', $i];
|
2018-03-27 04:13:10 +02:00
|
|
|
} else {
|
2019-06-23 05:30:40 +02:00
|
|
|
$type_tokens[++$rtc] = ['...', $i];
|
2018-03-27 04:13:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$was_char = true;
|
2019-08-08 23:25:56 +02:00
|
|
|
$was_space = false;
|
2018-03-27 04:13:10 +02:00
|
|
|
|
|
|
|
$i += 2;
|
2018-05-20 23:19:53 +02:00
|
|
|
|
|
|
|
continue;
|
2016-07-12 06:50:16 +02:00
|
|
|
}
|
2018-05-20 23:19:53 +02:00
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
$type_tokens[$rtc][0] .= $char;
|
2018-05-20 23:19:53 +02:00
|
|
|
$was_char = false;
|
2019-08-08 23:25:56 +02:00
|
|
|
$was_space = false;
|
2016-07-12 06:50:16 +02:00
|
|
|
}
|
|
|
|
|
2019-12-27 16:34:51 +01:00
|
|
|
/** @var list<array{0: string, 1: int}> $type_tokens */
|
2018-05-20 23:19:53 +02:00
|
|
|
self::$memoized_tokens[$string_type] = $type_tokens;
|
2018-02-04 18:23:32 +01:00
|
|
|
|
2018-05-20 23:19:53 +02:00
|
|
|
return $type_tokens;
|
2016-07-12 06:50:16 +02:00
|
|
|
}
|
|
|
|
|
2018-02-04 18:23:32 +01:00
|
|
|
/**
|
2019-01-10 18:13:49 +01:00
|
|
|
* @param array<string, mixed>|null $template_type_map
|
2019-06-23 05:30:40 +02:00
|
|
|
* @param array<string, array<int, array{0: string, 1: int}>>|null $type_aliases
|
2018-02-04 18:23:32 +01:00
|
|
|
*
|
2019-11-21 17:03:18 +01:00
|
|
|
* @return list<array{0: string, 1: int}>
|
2018-02-04 18:23:32 +01:00
|
|
|
*/
|
|
|
|
public static function fixUpLocalType(
|
2019-06-21 05:38:10 +02:00
|
|
|
string $string_type,
|
2018-02-04 18:23:32 +01:00
|
|
|
Aliases $aliases,
|
2018-12-18 05:29:27 +01:00
|
|
|
array $template_type_map = null,
|
2019-06-21 05:38:10 +02:00
|
|
|
array $type_aliases = null,
|
|
|
|
?string $self_fqcln = null,
|
2019-12-31 15:10:14 +01:00
|
|
|
?string $parent_fqcln = null,
|
|
|
|
bool $allow_assertions = false
|
2018-02-04 18:23:32 +01:00
|
|
|
) {
|
2018-05-20 23:19:53 +02:00
|
|
|
$type_tokens = self::tokenize($string_type);
|
2018-02-04 18:23:32 +01:00
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
for ($i = 0, $l = count($type_tokens); $i < $l; ++$i) {
|
2018-05-20 23:19:53 +02:00
|
|
|
$string_type_token = $type_tokens[$i];
|
2018-04-02 06:39:59 +02:00
|
|
|
|
2018-05-03 19:15:16 +02:00
|
|
|
if (in_array(
|
2019-06-23 05:30:40 +02:00
|
|
|
$string_type_token[0],
|
2019-02-11 14:41:48 +01:00
|
|
|
[
|
2019-12-27 18:49:28 +01:00
|
|
|
'<', '>', '|', '?', ',', '{', '}', ':', '::', '[', ']', '(', ')', '&', '=', '...', 'as',
|
2019-02-11 14:41:48 +01:00
|
|
|
],
|
2018-05-03 19:15:16 +02:00
|
|
|
true
|
|
|
|
)) {
|
2018-02-04 18:23:32 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-08-01 22:10:12 +02:00
|
|
|
if ($string_type_token[0][0] === '\\'
|
|
|
|
&& strlen($string_type_token[0]) === 1
|
|
|
|
) {
|
|
|
|
throw new TypeParseTreeException("Backslash \"\\\" has to be part of class name.");
|
|
|
|
}
|
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
if ($string_type_token[0][0] === '"'
|
|
|
|
|| $string_type_token[0][0] === '\''
|
|
|
|
|| $string_type_token[0] === '0'
|
|
|
|
|| preg_match('/[1-9]/', $string_type_token[0][0])
|
2018-05-20 23:19:53 +02:00
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
if (isset($type_tokens[$i + 1]) && $type_tokens[$i + 1][0] === ':') {
|
2018-02-04 18:23:32 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
if ($i > 0 && $type_tokens[$i - 1][0] === '::') {
|
2018-05-21 06:46:56 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
if (strpos($string_type_token[0], '$')) {
|
|
|
|
$string_type_token[0] = preg_replace('/(.+)\$.*/', '$1', $string_type_token[0]);
|
2019-02-21 22:13:31 +01:00
|
|
|
}
|
|
|
|
|
2020-01-10 17:29:10 +01:00
|
|
|
$fixed_token = !isset($type_tokens[$i + 1]) || $type_tokens[$i + 1][0] !== '('
|
|
|
|
? self::fixScalarTerms($string_type_token[0])
|
|
|
|
: $string_type_token[0];
|
|
|
|
|
|
|
|
$type_tokens[$i][0] = $fixed_token;
|
|
|
|
$string_type_token[0] = $fixed_token;
|
2019-06-21 05:38:10 +02:00
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
if ($string_type_token[0] === 'self' && $self_fqcln) {
|
|
|
|
$type_tokens[$i][0] = $self_fqcln;
|
2019-06-21 05:38:10 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
if ($string_type_token[0] === 'parent' && $parent_fqcln) {
|
|
|
|
$type_tokens[$i][0] = $parent_fqcln;
|
2019-06-21 05:38:10 +02:00
|
|
|
continue;
|
|
|
|
}
|
2018-02-04 18:23:32 +01:00
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
if (isset(self::PSALM_RESERVED_WORDS[$string_type_token[0]])) {
|
2018-04-02 06:39:59 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
if (isset($template_type_map[$string_type_token[0]])) {
|
2018-04-02 06:39:59 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-12-27 18:49:28 +01:00
|
|
|
if ($i > 1
|
|
|
|
&& ($type_tokens[$i - 2][0] === 'class-string-map')
|
|
|
|
&& ($type_tokens[$i - 1][0] === '<')
|
|
|
|
) {
|
|
|
|
$template_type_map[$string_type_token[0]] = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-05-20 23:19:53 +02:00
|
|
|
if (isset($type_tokens[$i + 1])) {
|
2019-06-23 05:30:40 +02:00
|
|
|
$next_char = $type_tokens[$i + 1][0];
|
2018-04-02 06:39:59 +02:00
|
|
|
if ($next_char === ':') {
|
2018-02-04 18:23:32 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
if ($next_char === '?' && isset($type_tokens[$i + 2]) && $type_tokens[$i + 2][0] === ':') {
|
2018-04-02 06:39:59 +02:00
|
|
|
continue;
|
|
|
|
}
|
2018-02-04 18:23:32 +01:00
|
|
|
}
|
2018-04-02 06:39:59 +02:00
|
|
|
|
2019-08-08 23:25:56 +02:00
|
|
|
if ($string_type_token[0][0] === '$' || $string_type_token[0][0] === ' ') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($type_tokens[$i + 1]) && $type_tokens[$i + 1][0] === '(') {
|
2018-04-02 06:39:59 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-12-31 15:10:14 +01:00
|
|
|
if ($allow_assertions && $string_type_token[0] === 'falsy') {
|
|
|
|
$type_tokens[$i][0] = 'false-y';
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
if (isset($type_aliases[$string_type_token[0]])) {
|
|
|
|
$replacement_tokens = $type_aliases[$string_type_token[0]];
|
2018-07-15 23:23:17 +02:00
|
|
|
|
2019-06-23 05:30:40 +02:00
|
|
|
array_unshift($replacement_tokens, ['(', $i]);
|
|
|
|
array_push($replacement_tokens, [')', $i]);
|
2018-08-09 04:33:31 +02:00
|
|
|
|
2018-07-15 23:23:17 +02:00
|
|
|
$diff = count($replacement_tokens) - 1;
|
|
|
|
|
2018-08-09 04:33:31 +02:00
|
|
|
array_splice($type_tokens, $i, 1, $replacement_tokens);
|
2018-07-15 23:23:17 +02:00
|
|
|
|
|
|
|
$i += $diff;
|
|
|
|
$l += $diff;
|
|
|
|
} else {
|
2019-06-23 05:30:40 +02:00
|
|
|
$type_tokens[$i][0] = self::getFQCLNFromString(
|
|
|
|
$string_type_token[0],
|
2018-07-15 23:23:17 +02:00
|
|
|
$aliases
|
|
|
|
);
|
|
|
|
}
|
2018-02-04 18:23:32 +01:00
|
|
|
}
|
|
|
|
|
2019-11-21 17:03:18 +01:00
|
|
|
/** @var list<array{0: string, 1: int}> */
|
2018-05-20 23:19:53 +02:00
|
|
|
return $type_tokens;
|
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
|
|
|
/**
|
|
|
|
* @param array<string, string> $aliased_classes
|
|
|
|
*/
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
} elseif (!$namespace && stripos($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
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2018-05-20 23:31:04 +02:00
|
|
|
public static function getInt($from_calculation = false, $value = null)
|
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
|
|
|
}
|
|
|
|
|
2018-02-16 01:50:50 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
|
|
|
public static function getNumeric()
|
|
|
|
{
|
|
|
|
$type = new TNumeric;
|
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
2018-05-18 17:02:50 +02:00
|
|
|
* @param string|null $value
|
2018-05-03 19:56:30 +02:00
|
|
|
*
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2018-05-20 23:31:04 +02:00
|
|
|
public static function getString($value = null)
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-22 16:01:25 +02:00
|
|
|
if (!$type && strlen($value) < $config->max_string_length) {
|
2019-03-07 20:56:18 +01:00
|
|
|
$type = new TLiteralString($value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2018-08-21 17:40:29 +02:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
|
|
|
public static function getSingleLetter()
|
|
|
|
{
|
|
|
|
$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
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2019-01-04 18:28:00 +01:00
|
|
|
public static function getClassString($extends = 'object')
|
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
|
|
|
|
2019-01-04 18:28:00 +01:00
|
|
|
/**
|
|
|
|
* @param string $class_type
|
|
|
|
*
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
|
|
|
public static function getLiteralClassString($class_type)
|
|
|
|
{
|
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]);
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2016-09-09 22:21:49 +02:00
|
|
|
public static function getNull()
|
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
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2018-12-08 19:18:55 +01:00
|
|
|
public static function getMixed($from_loop_isset = false)
|
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
|
|
|
}
|
|
|
|
|
2019-12-09 23:06:00 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
|
|
|
public static function getScalar()
|
|
|
|
{
|
2019-12-09 23:35:50 +01:00
|
|
|
$type = new TScalar();
|
2019-12-09 23:06:00 +01:00
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2018-05-18 17:02:50 +02:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
|
|
|
public static function getEmpty()
|
|
|
|
{
|
|
|
|
$type = new TEmpty();
|
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2016-09-09 22:21:49 +02:00
|
|
|
public static function getBool()
|
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
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
2018-05-18 17:02:50 +02:00
|
|
|
* @param float|null $value
|
2018-05-03 19:56:30 +02:00
|
|
|
*
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2018-05-20 23:31:04 +02:00
|
|
|
public static function getFloat($value = null)
|
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
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2016-09-09 22:21:49 +02:00
|
|
|
public static function getObject()
|
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
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2016-10-21 00:05:28 +02:00
|
|
|
public static function getClosure()
|
|
|
|
{
|
2017-01-15 01:06:58 +01:00
|
|
|
$type = new TNamedObject('Closure');
|
2016-10-21 00:05:28 +02:00
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2019-01-05 06:15:53 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
|
|
|
public static function getArrayKey()
|
|
|
|
{
|
|
|
|
$type = new TArrayKey();
|
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2016-09-09 22:21:49 +02:00
|
|
|
public static function getArray()
|
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
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2016-09-12 06:02:26 +02:00
|
|
|
public static function getEmptyArray()
|
|
|
|
{
|
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
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2019-10-10 16:57:43 +02:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
|
|
|
public static function getList()
|
|
|
|
{
|
|
|
|
$type = new TList(new Type\Union([new TMixed]));
|
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2016-09-09 22:21:49 +02:00
|
|
|
public static function getVoid()
|
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
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2016-09-09 22:21:49 +02:00
|
|
|
public static function getFalse()
|
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
|
|
|
}
|
|
|
|
|
2017-12-09 20:53:39 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
|
|
|
public static function getTrue()
|
|
|
|
{
|
|
|
|
$type = new TTrue;
|
|
|
|
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
2017-12-29 18:29:36 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
|
|
|
public static function getResource()
|
|
|
|
{
|
|
|
|
return new Union([new TResource]);
|
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
/**
|
|
|
|
* Combines two union types into one
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2016-06-16 02:16:40 +02:00
|
|
|
* @param Union $type_1
|
|
|
|
* @param Union $type_2
|
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
|
|
|
* @return Union
|
|
|
|
*/
|
2018-11-28 16:41:49 +01:00
|
|
|
public static function combineUnionTypes(
|
|
|
|
Union $type_1,
|
|
|
|
Union $type_2,
|
2019-01-05 06:15:53 +01: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
|
2018-11-28 16:41:49 +01:00
|
|
|
) {
|
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 {
|
|
|
|
return $type_2;
|
|
|
|
}
|
|
|
|
} elseif ($type_2->failed_reconciliation) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-08-31 15:49:32 +02:00
|
|
|
if ($type_1->external_mutation_free && $type_2->external_mutation_free) {
|
|
|
|
$combined_type->external_mutation_free = true;
|
|
|
|
}
|
|
|
|
|
2018-03-23 18:14:00 +01:00
|
|
|
if ($both_failed_reconciliation) {
|
|
|
|
$combined_type->failed_reconciliation = true;
|
|
|
|
}
|
2019-08-04 16:37:36 +02:00
|
|
|
|
|
|
|
if ($type_1->tainted || $type_2->tainted) {
|
|
|
|
$combined_type->tainted = $type_1->tainted & $type_2->tainted;
|
|
|
|
}
|
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;
|
|
|
|
}
|
|
|
|
|
2019-08-04 16:37:36 +02:00
|
|
|
if ($type_1->sources || $type_2->sources) {
|
|
|
|
$combined_type->sources = \array_unique(
|
|
|
|
array_merge($type_1->sources ?: [], $type_2->sources ?: [])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
*
|
|
|
|
* @param Union $type_1
|
|
|
|
* @param Union $type_2
|
|
|
|
*
|
2019-12-20 02:42:57 +01:00
|
|
|
* @return ?Union
|
2019-05-24 18:48:37 +02:00
|
|
|
*/
|
|
|
|
public static function intersectUnionTypes(
|
|
|
|
Union $type_1,
|
|
|
|
Union $type_2
|
|
|
|
) {
|
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) {
|
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;
|
|
|
|
}
|
|
|
|
|
2019-05-17 00:36:36 +02:00
|
|
|
public static function clearCache() : void
|
|
|
|
{
|
|
|
|
self::$memoized_tokens = [];
|
|
|
|
}
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|