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
|
|
|
|
2016-07-26 00:37:44 +02:00
|
|
|
use Psalm\Type\Atomic;
|
|
|
|
use Psalm\Type\Generic;
|
|
|
|
use Psalm\Type\Union;
|
|
|
|
use Psalm\Type\ParseTree;
|
2016-06-14 07:23:57 +02:00
|
|
|
|
|
|
|
abstract class Type
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Parses a string type representation
|
2016-08-04 20:43:33 +02:00
|
|
|
* @param string $type_string
|
2016-06-28 20:28:45 +02:00
|
|
|
* @return Union
|
2016-06-14 07:23:57 +02:00
|
|
|
*/
|
2016-06-28 20:28:45 +02:00
|
|
|
public static function parseString($type_string)
|
2016-06-14 07:23:57 +02:00
|
|
|
{
|
2016-06-27 04:02:23 +02:00
|
|
|
if (strpos($type_string, '[') !== false) {
|
2016-07-12 06:50:16 +02:00
|
|
|
$type_string = self::convertSquareBrackets($type_string);
|
2016-06-27 04:02:23 +02:00
|
|
|
}
|
|
|
|
|
2016-08-04 20:43:33 +02:00
|
|
|
$type_string = str_replace('?', 'null|', $type_string);
|
|
|
|
|
2016-07-12 06:50:16 +02:00
|
|
|
$type_tokens = self::tokenize($type_string);
|
2016-06-14 07:23:57 +02:00
|
|
|
|
|
|
|
if (count($type_tokens) === 1) {
|
2016-07-12 06:50:16 +02:00
|
|
|
$type_tokens[0] = self::fixScalarTerms($type_tokens[0]);
|
2016-06-24 00:45:46 +02:00
|
|
|
|
2016-06-28 20:28:45 +02:00
|
|
|
return new Union([new Atomic($type_tokens[0])]);
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// We construct a parse tree corresponding to the type
|
|
|
|
$parse_tree = new ParseTree(null, null);
|
|
|
|
|
2016-06-20 07:05:44 +02:00
|
|
|
$current_parent = null;
|
2016-06-14 07:23:57 +02:00
|
|
|
$current_leaf = $parse_tree;
|
|
|
|
|
|
|
|
while ($type_tokens) {
|
|
|
|
$type_token = array_shift($type_tokens);
|
|
|
|
|
|
|
|
switch ($type_token) {
|
|
|
|
case '<':
|
|
|
|
$current_parent = $current_leaf->parent;
|
|
|
|
$new_parent_leaf = new ParseTree(ParseTree::GENERIC, $current_parent);
|
|
|
|
$new_parent_leaf->children = [$current_leaf];
|
|
|
|
$current_leaf->parent = $new_parent_leaf;
|
|
|
|
|
|
|
|
if ($current_parent) {
|
|
|
|
array_pop($current_parent->children);
|
|
|
|
$current_parent->children[] = $new_parent_leaf;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$parse_tree = $new_parent_leaf;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '>':
|
|
|
|
while ($current_leaf->value !== ParseTree::GENERIC) {
|
|
|
|
if ($current_leaf->parent === null) {
|
|
|
|
throw new \InvalidArgumentException('Cannot parse generic type');
|
|
|
|
}
|
|
|
|
|
|
|
|
$current_leaf = $current_leaf->parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ',':
|
2016-08-11 01:21:03 +02:00
|
|
|
if (!$current_parent || $current_parent->value !== ParseTree::GENERIC) {
|
2016-06-14 07:23:57 +02:00
|
|
|
throw new \InvalidArgumentException('Cannot parse comma in non-generic type');
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '|':
|
|
|
|
$current_parent = $current_leaf->parent;
|
|
|
|
|
|
|
|
if ($current_parent && $current_parent->value === ParseTree::UNION) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$new_parent_leaf = new ParseTree(ParseTree::UNION, $current_parent);
|
|
|
|
$new_parent_leaf->children = [$current_leaf];
|
|
|
|
$current_leaf->parent = $new_parent_leaf;
|
|
|
|
|
|
|
|
if ($current_parent) {
|
|
|
|
array_pop($current_parent->children);
|
|
|
|
$current_parent->children[] = $new_parent_leaf;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$parse_tree = $new_parent_leaf;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
if ($current_leaf->value === null) {
|
|
|
|
$current_leaf->value = $type_token;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$new_leaf = new ParseTree($type_token, $current_leaf->parent);
|
|
|
|
$current_leaf->parent->children[] = $new_leaf;
|
|
|
|
|
|
|
|
$current_leaf = $new_leaf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-15 01:22:29 +02:00
|
|
|
$parsed_type = self::getTypeFromTree($parse_tree);
|
|
|
|
|
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-07-12 06:50:16 +02:00
|
|
|
private static function replaceScalarTerms($type_string)
|
|
|
|
{
|
|
|
|
if ($type_string === 'boolean') {
|
|
|
|
return 'bool';
|
|
|
|
}
|
|
|
|
elseif ($type_string === 'integer') {
|
|
|
|
return 'int';
|
|
|
|
}
|
|
|
|
elseif(in_array($type_string, ['String', 'Int', 'Float', 'Array', 'Object', 'Bool'])) {
|
|
|
|
return strtolower($type_string);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type_string;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function fixScalarTerms($type_string)
|
|
|
|
{
|
2016-07-13 17:18:07 +02:00
|
|
|
if (in_array(strtolower($type_string), ['numeric', 'int', 'float', 'string', 'bool', 'true', 'false', 'null', 'array', 'object', 'mixed', 'resource'])) {
|
2016-07-12 06:50:16 +02:00
|
|
|
return strtolower($type_string);
|
|
|
|
}
|
|
|
|
elseif ($type_string === 'boolean') {
|
|
|
|
return 'bool';
|
|
|
|
}
|
|
|
|
elseif ($type_string === 'integer') {
|
|
|
|
return 'int';
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type_string;
|
|
|
|
}
|
|
|
|
|
2016-06-14 07:23:57 +02:00
|
|
|
private static function getTypeFromTree(ParseTree $parse_tree)
|
|
|
|
{
|
|
|
|
if ($parse_tree->value === ParseTree::GENERIC) {
|
|
|
|
$generic_type = array_shift($parse_tree->children);
|
|
|
|
|
|
|
|
$generic_params = array_map(
|
|
|
|
function (ParseTree $child_tree) {
|
2016-07-24 23:08:40 +02:00
|
|
|
$tree_type = self::getTypeFromTree($child_tree);
|
|
|
|
return $tree_type instanceof Union ? $tree_type : new Union([$tree_type]);
|
2016-06-14 07:23:57 +02:00
|
|
|
},
|
|
|
|
$parse_tree->children
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!$generic_params) {
|
|
|
|
throw new \InvalidArgumentException('No generic params provided for type');
|
|
|
|
}
|
|
|
|
|
2016-07-24 23:08:40 +02:00
|
|
|
return new Generic(self::fixScalarTerms($generic_type->value), $generic_params);
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($parse_tree->value === ParseTree::UNION) {
|
|
|
|
$union_types = array_map(
|
|
|
|
function (ParseTree $child_tree) {
|
|
|
|
return self::getTypeFromTree($child_tree);
|
|
|
|
},
|
|
|
|
$parse_tree->children
|
|
|
|
);
|
|
|
|
|
|
|
|
return new Union($union_types);
|
|
|
|
}
|
|
|
|
|
2016-07-12 06:50:16 +02:00
|
|
|
return new Atomic(self::fixScalarTerms($parse_tree->value));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array<string>
|
|
|
|
*/
|
|
|
|
public static function tokenize($return_type)
|
|
|
|
{
|
|
|
|
$return_type_tokens = [''];
|
|
|
|
$was_char = false;
|
|
|
|
|
|
|
|
foreach (str_split($return_type) as $char) {
|
|
|
|
if ($was_char) {
|
|
|
|
$return_type_tokens[] = '';
|
|
|
|
}
|
|
|
|
|
2016-08-07 18:50:21 +02:00
|
|
|
if ($char === '<' || $char === '>' || $char === '|' || $char === '?') {
|
2016-07-12 06:50:16 +02:00
|
|
|
if ($return_type_tokens[count($return_type_tokens) - 1] === '') {
|
|
|
|
$return_type_tokens[count($return_type_tokens) - 1] = $char;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$return_type_tokens[] = $char;
|
|
|
|
}
|
|
|
|
|
|
|
|
$was_char = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$return_type_tokens[count($return_type_tokens) - 1] .= $char;
|
|
|
|
$was_char = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $return_type_tokens;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function convertSquareBrackets($type)
|
|
|
|
{
|
|
|
|
return preg_replace_callback(
|
2016-08-11 01:21:03 +02:00
|
|
|
'/([a-zA-Z\<\>\\\\_]+)((\[\])+)/',
|
2016-07-12 06:50:16 +02:00
|
|
|
function ($matches) {
|
|
|
|
$inner_type = $matches[1];
|
|
|
|
|
|
|
|
$dimensionality = strlen($matches[2]) / 2;
|
|
|
|
|
|
|
|
for ($i = 0; $i < $dimensionality; $i++) {
|
|
|
|
$inner_type = 'array<' . $inner_type . '>';
|
|
|
|
}
|
|
|
|
|
|
|
|
return $inner_type;
|
|
|
|
},
|
|
|
|
$type
|
|
|
|
);
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|
2016-06-15 01:22:29 +02:00
|
|
|
|
|
|
|
public static function getInt($enclose_with_union = true)
|
|
|
|
{
|
|
|
|
$type = new Atomic('int');
|
|
|
|
|
|
|
|
if ($enclose_with_union) {
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getString($enclose_with_union = true)
|
|
|
|
{
|
|
|
|
$type = new Atomic('string');
|
|
|
|
|
|
|
|
if ($enclose_with_union) {
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
public static function getNull($enclose_with_union = true)
|
|
|
|
{
|
|
|
|
$type = new Atomic('null');
|
|
|
|
|
|
|
|
if ($enclose_with_union) {
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
|
2016-06-15 01:22:29 +02:00
|
|
|
public static function getMixed($enclose_with_union = true)
|
|
|
|
{
|
|
|
|
$type = new Atomic('mixed');
|
|
|
|
|
|
|
|
if ($enclose_with_union) {
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
public static function getBool($enclose_with_union = true)
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
|
|
|
$type = new Atomic('bool');
|
|
|
|
|
|
|
|
if ($enclose_with_union) {
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getDouble($enclose_with_union = true)
|
|
|
|
{
|
|
|
|
$type = new Atomic('double');
|
|
|
|
|
|
|
|
if ($enclose_with_union) {
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getFloat($enclose_with_union = true)
|
|
|
|
{
|
|
|
|
$type = new Atomic('float');
|
|
|
|
|
|
|
|
if ($enclose_with_union) {
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getObject($enclose_with_union = true)
|
|
|
|
{
|
|
|
|
$type = new Atomic('object');
|
|
|
|
|
|
|
|
if ($enclose_with_union) {
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getArray($enclose_with_union = true)
|
|
|
|
{
|
|
|
|
$type = new Atomic('array');
|
|
|
|
|
|
|
|
if ($enclose_with_union) {
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
public static function getVoid($enclose_with_union = true)
|
|
|
|
{
|
|
|
|
$type = new Atomic('void');
|
|
|
|
|
|
|
|
if ($enclose_with_union) {
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getFalse($enclose_with_union = true)
|
|
|
|
{
|
|
|
|
$type = new Atomic('false');
|
|
|
|
|
|
|
|
if ($enclose_with_union) {
|
|
|
|
return new Union([$type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
|
2016-06-15 01:22:29 +02:00
|
|
|
public function isMixed()
|
|
|
|
{
|
|
|
|
if ($this instanceof Atomic) {
|
|
|
|
return $this->value === 'mixed';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this instanceof Union) {
|
2016-06-17 19:21:57 +02:00
|
|
|
return isset($this->types['mixed']);
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
public function isNull()
|
|
|
|
{
|
|
|
|
if ($this instanceof Atomic) {
|
|
|
|
return $this->value === 'null';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this instanceof Union) {
|
2016-06-17 19:21:57 +02:00
|
|
|
return count($this->types) === 1 && isset($this->types['null']);
|
2016-06-16 02:16:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-17 22:58:15 +02:00
|
|
|
public function isString()
|
|
|
|
{
|
|
|
|
if ($this instanceof Atomic) {
|
|
|
|
return $this->value === 'string';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this instanceof Union) {
|
2016-08-11 23:36:22 +02:00
|
|
|
return isset($this->types['string']);
|
2016-06-17 22:58:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 07:19:52 +02:00
|
|
|
public function isVoid()
|
|
|
|
{
|
|
|
|
if ($this instanceof Atomic) {
|
|
|
|
return $this->value === 'void';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this instanceof Union) {
|
2016-06-17 19:21:57 +02:00
|
|
|
return isset($this->types['void']);
|
2016-06-16 07:19:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-13 17:18:07 +02:00
|
|
|
public function isNumeric()
|
|
|
|
{
|
|
|
|
if ($this instanceof Atomic) {
|
|
|
|
return $this->value === 'numeric';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this instanceof Union) {
|
|
|
|
return isset($this->types['numeric']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-19 00:17:47 +02:00
|
|
|
public function isCallable()
|
|
|
|
{
|
|
|
|
if ($this instanceof Atomic) {
|
|
|
|
return $this->value === 'callable';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this instanceof Union) {
|
|
|
|
return isset($this->types['callable']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-12 07:28:41 +02:00
|
|
|
public function isEmpty()
|
|
|
|
{
|
2016-07-19 00:17:47 +02:00
|
|
|
if ($this instanceof Atomic) {
|
|
|
|
return $this->value === 'empty';
|
|
|
|
}
|
|
|
|
|
2016-07-25 00:02:03 +02:00
|
|
|
if ($this instanceof Union) {
|
|
|
|
return isset($this->types['empty']);
|
|
|
|
}
|
2016-07-12 07:28:41 +02:00
|
|
|
}
|
|
|
|
|
2016-07-12 02:11:00 +02:00
|
|
|
public function isObject()
|
|
|
|
{
|
|
|
|
if ($this instanceof Atomic) {
|
|
|
|
return $this->value === 'object';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this instanceof Union) {
|
|
|
|
return isset($this->types['object']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-07 02:27:13 +02:00
|
|
|
public function isArray()
|
|
|
|
{
|
|
|
|
if ($this instanceof Atomic) {
|
|
|
|
return $this->value === 'array';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this instanceof Union) {
|
|
|
|
return isset($this->types['array']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
public function isNullable()
|
|
|
|
{
|
|
|
|
if ($this instanceof Atomic) {
|
2016-06-17 19:21:57 +02:00
|
|
|
return $this->value === 'null';
|
2016-06-16 02:16:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($this instanceof Union) {
|
2016-06-17 19:21:57 +02:00
|
|
|
return isset($this->types['null']);
|
2016-06-16 02:16:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-07-12 01:58:37 +02:00
|
|
|
public function hasGeneric()
|
|
|
|
{
|
|
|
|
if ($this instanceof Union) {
|
|
|
|
foreach ($this->types as $type) {
|
|
|
|
if ($type instanceof Generic) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this instanceof Generic;
|
|
|
|
}
|
|
|
|
|
2016-07-13 17:18:07 +02:00
|
|
|
public function isScalarType()
|
2016-06-16 07:19:52 +02:00
|
|
|
{
|
|
|
|
if ($this instanceof Atomic) {
|
|
|
|
return $this->value === 'int' ||
|
|
|
|
$this->value === 'string' ||
|
|
|
|
$this->value === 'double' ||
|
|
|
|
$this->value === 'float' ||
|
|
|
|
$this->value === 'bool' ||
|
|
|
|
$this->value === 'false';
|
|
|
|
}
|
2016-07-12 01:58:37 +02:00
|
|
|
|
|
|
|
return false;
|
2016-06-16 07:19:52 +02:00
|
|
|
}
|
|
|
|
|
2016-07-13 17:18:07 +02:00
|
|
|
public function isNumericType()
|
|
|
|
{
|
|
|
|
if ($this instanceof Atomic) {
|
|
|
|
return $this->value === 'int' ||
|
|
|
|
$this->value === 'double' ||
|
|
|
|
$this->value === 'float';
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-08-05 23:45:47 +02:00
|
|
|
public function isObjectType()
|
|
|
|
{
|
2016-08-07 02:27:13 +02:00
|
|
|
return $this->isObject() || (!$this->isScalarType() && !$this->isCallable() && !$this->isArray());
|
2016-08-05 23:45:47 +02:00
|
|
|
}
|
|
|
|
|
2016-07-26 00:30:49 +02:00
|
|
|
/**
|
|
|
|
* @param array<Union> $redefined_vars
|
|
|
|
* @param Context $context
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function redefineGenericUnionTypes(array $redefined_vars, Context $context)
|
|
|
|
{
|
|
|
|
foreach ($redefined_vars as $var_name => $redefined_union_type) {
|
|
|
|
foreach ($redefined_union_type->types as $redefined_atomic_type) {
|
|
|
|
foreach ($context->vars_in_scope[$var_name]->types as $context_type) {
|
|
|
|
if ($context_type instanceof Type\Generic &&
|
|
|
|
$redefined_atomic_type instanceof Type\Generic &&
|
|
|
|
$context_type->value === $redefined_atomic_type->value
|
|
|
|
) {
|
|
|
|
if ($context_type->type_params[0]->isEmpty()) {
|
|
|
|
$context_type->type_params[0] = $redefined_atomic_type->type_params[0];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$context_type->type_params[0] = Type::combineUnionTypes(
|
|
|
|
$redefined_atomic_type->type_params[0],
|
|
|
|
$context_type->type_params[0]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
/**
|
|
|
|
* Combines two union types into one
|
|
|
|
* @param Union $type_1
|
|
|
|
* @param Union $type_2
|
|
|
|
* @return Union
|
|
|
|
*/
|
|
|
|
public static function combineUnionTypes(Union $type_1, Union $type_2)
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2016-06-17 19:21:57 +02:00
|
|
|
return self::combineTypes(array_merge(array_values($type_1->types), array_values($type_2->types)));
|
2016-06-16 02:16:40 +02:00
|
|
|
}
|
2016-06-15 01:22:29 +02:00
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
/**
|
|
|
|
* Combines types together
|
|
|
|
* so int + string = int|string
|
|
|
|
* so array<int> + array<string> = array<int|string>
|
|
|
|
* and array<int> + string = array<int>|string
|
|
|
|
* and array<empty> + array<empty> = array<empty>
|
|
|
|
* and array<string> + array<empty> = array<string>
|
|
|
|
* and array + array<string> = array<mixed>
|
|
|
|
*
|
|
|
|
* @param array<Atomic> $types
|
|
|
|
* @return Union
|
|
|
|
*/
|
|
|
|
public static function combineTypes(array $types)
|
|
|
|
{
|
|
|
|
if (in_array(null, $types)) {
|
|
|
|
return Type::getMixed();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count($types) === 1) {
|
|
|
|
if ($types[0]->value === 'false') {
|
|
|
|
$types[0]->value = 'bool';
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
return new Union([$types[0]]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$types) {
|
|
|
|
throw new \InvalidArgumentException('You must pass at least one type to combineTypes');
|
|
|
|
}
|
|
|
|
|
|
|
|
$value_types = [];
|
|
|
|
|
|
|
|
foreach ($types as $type) {
|
|
|
|
if ($type instanceof Union) {
|
|
|
|
throw new \InvalidArgumentException('Union type not expected here');
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we see the magic empty value and there's more than one type, ignore it
|
|
|
|
if ($type->value === 'empty') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($type->value === 'mixed') {
|
|
|
|
return Type::getMixed();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($type->value === 'void') {
|
|
|
|
$type->value = 'null';
|
|
|
|
}
|
|
|
|
|
|
|
|
// deal with false|bool => bool
|
|
|
|
if ($type->value === 'false' && isset($value_types['bool'])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
elseif ($type->value === 'bool' && isset($value_types['false'])) {
|
|
|
|
unset($value_types['false']);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($value_types[$type->value])) {
|
|
|
|
$value_types[$type->value] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
// @todo this doesn't support multiple type params right now
|
|
|
|
$value_types[$type->value][(string) $type] = $type instanceof Generic ? $type->type_params[0] : null;
|
|
|
|
}
|
|
|
|
|
2016-06-16 07:19:52 +02:00
|
|
|
if (count($value_types) === 1) {
|
|
|
|
if (isset($value_types['false'])) {
|
|
|
|
return self::getBool();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
$new_types = [];
|
|
|
|
|
|
|
|
foreach ($value_types as $key => $value_type) {
|
|
|
|
if (count($value_type) === 1) {
|
|
|
|
$value_type_param = array_values($value_type)[0];
|
|
|
|
$new_types[] = $value_type_param ? new Generic($key, [$value_type_param]) : new Atomic($key);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$expanded_value_types = [];
|
|
|
|
|
|
|
|
foreach ($value_types[$key] as $expandable_value_type) {
|
|
|
|
if ($expandable_value_type instanceof Union) {
|
2016-06-20 07:05:44 +02:00
|
|
|
$expanded_value_types = array_merge($expanded_value_types, array_values($expandable_value_type->types));
|
2016-06-16 02:16:40 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$expanded_value_types[] = $expandable_value_type;
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
// we have a generic type with
|
|
|
|
$new_types[] = new Generic($key, [self::combineTypes($expanded_value_types)]);
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
$new_types = array_values($new_types);
|
|
|
|
return new Union($new_types);
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|