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-11-21 05:31:10 +01:00
|
|
|
use Psalm\Exception\TypeParseTreeException;
|
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;
|
2017-01-15 01:06:58 +01:00
|
|
|
use Psalm\Type\Atomic\TBool;
|
2018-03-05 22:06:06 +01:00
|
|
|
use Psalm\Type\Atomic\TClassString;
|
2017-01-15 01:06:58 +01:00
|
|
|
use Psalm\Type\Atomic\TEmpty;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\Type\Atomic\TFalse;
|
|
|
|
use Psalm\Type\Atomic\TFloat;
|
|
|
|
use Psalm\Type\Atomic\TGenericObject;
|
|
|
|
use Psalm\Type\Atomic\TInt;
|
2017-01-15 01:06:58 +01:00
|
|
|
use Psalm\Type\Atomic\TMixed;
|
|
|
|
use Psalm\Type\Atomic\TNamedObject;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\Type\Atomic\TNull;
|
2018-02-16 01:50:50 +01:00
|
|
|
use Psalm\Type\Atomic\TNumeric;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\Type\Atomic\TObject;
|
2017-12-29 18:29:36 +01:00
|
|
|
use Psalm\Type\Atomic\TResource;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\Type\Atomic\TString;
|
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-07-26 00:37:44 +02:00
|
|
|
use Psalm\Type\ParseTree;
|
2017-06-29 17:18:02 +02:00
|
|
|
use Psalm\Type\TypeCombination;
|
2016-11-02 07:29:00 +01:00
|
|
|
use Psalm\Type\Union;
|
2016-06-14 07:23:57 +02:00
|
|
|
|
|
|
|
abstract class Type
|
|
|
|
{
|
2018-02-04 18:23:32 +01:00
|
|
|
/**
|
2018-02-22 00:59:31 +01:00
|
|
|
* @var array<string, array<int, string>>
|
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
|
2018-01-10 06:07:47 +01:00
|
|
|
* @param bool $php_compatible
|
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-01-10 06:07:47 +01:00
|
|
|
public static function parseString($type_string, $php_compatible = false)
|
2016-06-14 07:23:57 +02:00
|
|
|
{
|
2016-11-21 05:31:10 +01:00
|
|
|
// remove all unacceptable characters
|
2018-03-18 18:57:04 +01:00
|
|
|
$type_string = preg_replace('/[^A-Za-z0-9\-_\\\\&|\? \<\>\{\}:,\]\[\(\)\$]/', '', trim($type_string));
|
2016-11-21 05:31:10 +01: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
|
|
|
}
|
|
|
|
|
2017-11-20 06:37:45 +01:00
|
|
|
$type_string = preg_replace('/\?(?=[a-zA-Z])/', '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) {
|
2018-01-10 06:07:47 +01:00
|
|
|
$type_tokens[0] = self::fixScalarTerms($type_tokens[0], $php_compatible);
|
2016-06-24 00:45:46 +02:00
|
|
|
|
2018-01-10 06:07:47 +01:00
|
|
|
return new Union([Atomic::create($type_tokens[0], $php_compatible)]);
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|
|
|
|
|
2016-11-21 05:31:10 +01:00
|
|
|
try {
|
|
|
|
$parse_tree = ParseTree::createFromTokens($type_tokens);
|
2018-01-10 06:07:47 +01:00
|
|
|
$parsed_type = self::getTypeFromTree($parse_tree, $php_compatible);
|
2017-01-15 01:06:58 +01:00
|
|
|
} catch (TypeParseTreeException $e) {
|
2016-11-21 05:31:10 +01:00
|
|
|
throw $e;
|
|
|
|
}
|
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
|
2018-01-10 06:07:47 +01:00
|
|
|
* @param bool $php_compatible
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return string
|
|
|
|
*/
|
2018-02-04 18:23:32 +01:00
|
|
|
private static function fixScalarTerms($type_string, $php_compatible = false)
|
2016-07-12 06:50:16 +02:00
|
|
|
{
|
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':
|
|
|
|
case 'resource':
|
|
|
|
return $type_string_lc;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ($type_string) {
|
|
|
|
case 'boolean':
|
|
|
|
return $php_compatible ? $type_string : 'bool';
|
|
|
|
|
|
|
|
case 'integer':
|
|
|
|
return $php_compatible ? $type_string : 'int';
|
|
|
|
|
|
|
|
case 'double':
|
|
|
|
case 'real':
|
|
|
|
return $php_compatible ? $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
|
|
|
|
* @param bool $php_compatible
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-15 01:06:58 +01:00
|
|
|
* @return Atomic|TArray|TGenericObject|ObjectLike|Union
|
2016-11-02 07:29:00 +01:00
|
|
|
*/
|
2018-01-10 06:07:47 +01:00
|
|
|
private static function getTypeFromTree(ParseTree $parse_tree, $php_compatible)
|
2016-06-14 07:23:57 +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
|
|
|
|
|
|
|
$generic_params = array_map(
|
2016-12-07 20:13:39 +01:00
|
|
|
/**
|
|
|
|
* @return Union
|
|
|
|
*/
|
2016-06-14 07:23:57 +02:00
|
|
|
function (ParseTree $child_tree) {
|
2018-01-10 06:07:47 +01:00
|
|
|
$tree_type = self::getTypeFromTree($child_tree, false);
|
2017-05-25 04:07:49 +02:00
|
|
|
|
2016-07-24 23:08:40 +02:00
|
|
|
return $tree_type instanceof Union ? $tree_type : new Union([$tree_type]);
|
2016-06-14 07:23:57 +02:00
|
|
|
},
|
|
|
|
$parse_tree->children
|
|
|
|
);
|
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
$generic_type_value = self::fixScalarTerms($generic_type, false);
|
2016-09-09 22:21:49 +02:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
if (($generic_type_value === 'array' || $generic_type_value === 'Generator') &&
|
|
|
|
count($generic_params) === 1
|
|
|
|
) {
|
2017-01-15 01:06:58 +01:00
|
|
|
array_unshift($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) {
|
|
|
|
throw new \InvalidArgumentException('No generic params provided for type');
|
|
|
|
}
|
|
|
|
|
2016-09-22 04:15:46 +02:00
|
|
|
if ($generic_type_value === 'array') {
|
2017-01-15 01:06:58 +01:00
|
|
|
return new TArray($generic_params);
|
2016-09-22 04:15:46 +02:00
|
|
|
}
|
|
|
|
|
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) {
|
2016-06-14 07:23:57 +02:00
|
|
|
$union_types = array_map(
|
2016-12-07 20:13:39 +01:00
|
|
|
/**
|
|
|
|
* @return Atomic
|
|
|
|
*/
|
2016-06-14 07:23:57 +02:00
|
|
|
function (ParseTree $child_tree) {
|
2018-01-10 06:07:47 +01:00
|
|
|
$atomic_type = self::getTypeFromTree($child_tree, false);
|
2016-12-07 20:13:39 +01:00
|
|
|
|
|
|
|
if (!$atomic_type instanceof Atomic) {
|
|
|
|
throw new \UnexpectedValueException(
|
|
|
|
'Was expecting an atomic type, got ' . get_class($atomic_type)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $atomic_type;
|
2016-06-14 07:23:57 +02:00
|
|
|
},
|
|
|
|
$parse_tree->children
|
|
|
|
);
|
|
|
|
|
2016-11-20 08:52:34 +01:00
|
|
|
return self::combineTypes($union_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
|
|
|
|
*/
|
|
|
|
function (ParseTree $child_tree) {
|
|
|
|
$atomic_type = self::getTypeFromTree($child_tree, false);
|
|
|
|
|
|
|
|
if (!$atomic_type instanceof Atomic) {
|
|
|
|
throw new \UnexpectedValueException(
|
|
|
|
'Was expecting an atomic type, got ' . get_class($atomic_type)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $atomic_type;
|
|
|
|
},
|
|
|
|
$parse_tree->children
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach ($intersection_types as $intersection_type) {
|
|
|
|
if (!$intersection_type instanceof TNamedObject) {
|
|
|
|
throw new TypeParseTreeException('Intersection types must all be objects');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @var TNamedObject[] $intersection_types */
|
|
|
|
$first_type = array_shift($intersection_types);
|
|
|
|
|
|
|
|
$first_type->extra_types = $intersection_types;
|
|
|
|
|
|
|
|
return new Type\Union([$first_type]);
|
|
|
|
}
|
|
|
|
|
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) {
|
2018-01-10 06:07:47 +01:00
|
|
|
$property_type = self::getTypeFromTree($property_branch, false);
|
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) {
|
|
|
|
$property_type = self::getTypeFromTree($property_branch->children[0], false);
|
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 {
|
2018-03-21 01:19:26 +01:00
|
|
|
throw new \InvalidArgumentException(
|
|
|
|
'Unexpected number of property parts (' . count($property_branch->children) . ')'
|
|
|
|
);
|
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
|
|
|
}
|
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
if ($type !== 'array') {
|
2017-01-15 01:06:58 +01:00
|
|
|
throw new \InvalidArgumentException('Object-like type must be array');
|
2016-10-30 17:46:18 +01:00
|
|
|
}
|
|
|
|
|
2018-01-19 22:06:30 +01:00
|
|
|
if (!$properties) {
|
|
|
|
throw new \InvalidArgumentException('No properties supplied for ObjectLike');
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:06:58 +01:00
|
|
|
return new ObjectLike($properties);
|
2016-09-22 06:31:07 +02:00
|
|
|
}
|
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
if (!$parse_tree instanceof ParseTree\Value) {
|
|
|
|
throw new \InvalidArgumentException('Unrecognised parse tree type');
|
|
|
|
}
|
|
|
|
|
2018-01-10 06:07:47 +01:00
|
|
|
$atomic_type = self::fixScalarTerms($parse_tree->value, $php_compatible);
|
2016-09-09 22:21:49 +02:00
|
|
|
|
2018-01-10 06:07:47 +01:00
|
|
|
return Atomic::create($atomic_type, $php_compatible);
|
2016-07-12 06:50:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-10-30 17:46:18 +01:00
|
|
|
* @param string $return_type
|
2018-01-02 03:17:23 +01:00
|
|
|
* @param bool $ignore_space
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-03 17:38:59 +02:00
|
|
|
* @return array<int,string>
|
2016-07-12 06:50:16 +02:00
|
|
|
*/
|
2018-01-02 03:17:23 +01:00
|
|
|
public static function tokenize($return_type, $ignore_space = true)
|
2016-07-12 06:50:16 +02:00
|
|
|
{
|
|
|
|
$return_type_tokens = [''];
|
|
|
|
$was_char = false;
|
2018-01-02 03:17:23 +01:00
|
|
|
|
|
|
|
if ($ignore_space) {
|
|
|
|
$return_type = str_replace(' ', '', $return_type);
|
|
|
|
}
|
2016-07-12 06:50:16 +02:00
|
|
|
|
2018-02-04 18:23:32 +01:00
|
|
|
if (isset(self::$memoized_tokens[$return_type])) {
|
|
|
|
return self::$memoized_tokens[$return_type];
|
|
|
|
}
|
|
|
|
|
2018-01-21 03:22:33 +01:00
|
|
|
// index of last type token
|
|
|
|
$rtc = 0;
|
|
|
|
|
2016-07-12 06:50:16 +02:00
|
|
|
foreach (str_split($return_type) as $char) {
|
|
|
|
if ($was_char) {
|
2018-01-21 03:22:33 +01:00
|
|
|
$return_type_tokens[++$rtc] = '';
|
2016-07-12 06:50:16 +02:00
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
if ($char === '<' ||
|
|
|
|
$char === '>' ||
|
|
|
|
$char === '|' ||
|
|
|
|
$char === '?' ||
|
|
|
|
$char === ',' ||
|
|
|
|
$char === '{' ||
|
|
|
|
$char === '}' ||
|
2018-01-02 03:17:23 +01:00
|
|
|
$char === '[' ||
|
|
|
|
$char === ']' ||
|
2018-03-21 01:19:26 +01:00
|
|
|
$char === '(' ||
|
|
|
|
$char === ')' ||
|
2018-01-02 03:17:23 +01:00
|
|
|
$char === ' ' ||
|
2018-03-18 18:57:04 +01:00
|
|
|
$char === '&' ||
|
2016-11-02 07:29:00 +01:00
|
|
|
$char === ':'
|
|
|
|
) {
|
2018-01-21 03:22:33 +01:00
|
|
|
if ($return_type_tokens[$rtc] === '') {
|
|
|
|
$return_type_tokens[$rtc] = $char;
|
2016-11-02 07:29:00 +01:00
|
|
|
} else {
|
2018-01-21 03:22:33 +01:00
|
|
|
$return_type_tokens[++$rtc] = $char;
|
2016-07-12 06:50:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$was_char = true;
|
2016-11-02 07:29:00 +01:00
|
|
|
} else {
|
2018-01-21 03:22:33 +01:00
|
|
|
$return_type_tokens[$rtc] .= $char;
|
2016-07-12 06:50:16 +02:00
|
|
|
$was_char = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-04 18:23:32 +01:00
|
|
|
self::$memoized_tokens[$return_type] = $return_type_tokens;
|
|
|
|
|
2016-07-12 06:50:16 +02:00
|
|
|
return $return_type_tokens;
|
|
|
|
}
|
|
|
|
|
2018-02-04 18:23:32 +01:00
|
|
|
/**
|
|
|
|
* @param string $return_type
|
|
|
|
* @param Aliases $aliases
|
|
|
|
* @param array<string, string>|null $template_types
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public static function fixUpLocalType(
|
|
|
|
$return_type,
|
|
|
|
Aliases $aliases,
|
|
|
|
array $template_types = null
|
|
|
|
) {
|
|
|
|
if (strpos($return_type, '[') !== false) {
|
|
|
|
$return_type = self::convertSquareBrackets($return_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
$return_type_tokens = self::tokenize($return_type);
|
|
|
|
|
|
|
|
foreach ($return_type_tokens as $i => &$return_type_token) {
|
|
|
|
if (in_array($return_type_token, ['<', '>', '|', '?', ',', '{', '}', ':'], true)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($return_type_tokens[$i + 1]) && $return_type_tokens[$i + 1] === ':') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$return_type_token = self::fixScalarTerms($return_type_token);
|
|
|
|
|
|
|
|
if ($return_type_token[0] === strtoupper($return_type_token[0]) &&
|
|
|
|
!isset($template_types[$return_type_token])
|
|
|
|
) {
|
|
|
|
if ($return_type_token[0] === '$') {
|
|
|
|
if ($return_type === '$this') {
|
|
|
|
$return_type_token = 'static';
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$return_type_token = self::getFQCLNFromString(
|
|
|
|
$return_type_token,
|
|
|
|
$aliases
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return implode('', $return_type_tokens);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $class
|
|
|
|
* @param Aliases $aliases
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public static function getFQCLNFromString($class, Aliases $aliases)
|
|
|
|
{
|
|
|
|
if (empty($class)) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-10-30 17:46:18 +01:00
|
|
|
/**
|
|
|
|
* @param string $type
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-30 17:46:18 +01:00
|
|
|
* @return string
|
|
|
|
*/
|
2016-07-12 06:50:16 +02:00
|
|
|
public static function convertSquareBrackets($type)
|
|
|
|
{
|
2017-01-02 05:49:52 +01:00
|
|
|
$class_chars = '[a-zA-Z0-9\<\>\\\\_]+';
|
2017-05-25 04:07:49 +02:00
|
|
|
|
2016-07-12 06:50:16 +02:00
|
|
|
return preg_replace_callback(
|
2016-11-20 08:52:34 +01:00
|
|
|
'/(' . $class_chars . '|' . '\((' . $class_chars . '(\|' . $class_chars . ')*' . ')\))((\[\])+)/',
|
2016-12-07 20:13:39 +01:00
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
2016-11-22 01:07:56 +01:00
|
|
|
function (array $matches) {
|
2016-10-30 17:46:18 +01:00
|
|
|
$inner_type = str_replace(['(', ')'], '', (string)$matches[1]);
|
2016-07-12 06:50:16 +02:00
|
|
|
|
2016-11-20 08:52:34 +01:00
|
|
|
$dimensionality = strlen((string)$matches[4]) / 2;
|
2016-07-12 06:50:16 +02:00
|
|
|
|
2017-05-27 02:05:57 +02:00
|
|
|
for ($i = 0; $i < $dimensionality; ++$i) {
|
2016-11-20 08:52:34 +01:00
|
|
|
$inner_type = 'array<mixed,' . $inner_type . '>';
|
2016-07-12 06:50:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $inner_type;
|
|
|
|
},
|
|
|
|
$type
|
|
|
|
);
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|
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 getInt()
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2017-01-15 01:06:58 +01:00
|
|
|
$type = new TInt;
|
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-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
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2016-09-09 22:21:49 +02:00
|
|
|
public static function getString()
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2017-01-15 01:06:58 +01: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-03-05 22:06:06 +01:00
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
|
|
|
public static function getClassString()
|
|
|
|
{
|
|
|
|
$type = new TClassString;
|
|
|
|
|
|
|
|
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
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2016-09-09 22:21:49 +02:00
|
|
|
public static function getMixed()
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2017-01-15 01:06:58 +01:00
|
|
|
$type = new TMixed;
|
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 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
|
|
|
/**
|
|
|
|
* @return Type\Union
|
|
|
|
*/
|
2016-09-09 22:21:49 +02:00
|
|
|
public static function getFloat()
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2017-01-15 01:06:58 +01:00
|
|
|
$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]);
|
|
|
|
}
|
|
|
|
|
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
|
|
|
[
|
2017-01-15 01:06:58 +01:00
|
|
|
new Type\Union([new TMixed]),
|
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()
|
|
|
|
{
|
|
|
|
return new Type\Union([
|
2017-01-15 01:06:58 +01:00
|
|
|
new TArray(
|
2016-09-12 06:02:26 +02:00
|
|
|
[
|
2017-01-15 01:06:58 +01:00
|
|
|
new Type\Union([new TEmpty]),
|
2017-05-27 02:05:57 +02:00
|
|
|
new Type\Union([new TEmpty]),
|
2016-09-12 06:02:26 +02:00
|
|
|
]
|
2017-05-27 02:05:57 +02:00
|
|
|
),
|
2016-09-12 06:02:26 +02:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
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
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-06-16 02:16:40 +02:00
|
|
|
* @return Union
|
|
|
|
*/
|
2016-09-21 03:45:49 +02:00
|
|
|
public static function combineUnionTypes(Union $type_1, Union $type_2)
|
2016-06-15 01:22:29 +02:00
|
|
|
{
|
2018-02-08 20:02:32 +01:00
|
|
|
if ($type_1->isMixed() || $type_2->isMixed()) {
|
|
|
|
return Type::getMixed();
|
|
|
|
}
|
|
|
|
|
2017-03-13 23:06:56 +01:00
|
|
|
$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;
|
|
|
|
}
|
|
|
|
|
2018-01-09 21:05:48 +01:00
|
|
|
$combined_type = self::combineTypes(
|
|
|
|
array_merge(
|
|
|
|
array_values($type_1->getTypes()),
|
|
|
|
array_values($type_2->getTypes())
|
|
|
|
)
|
|
|
|
);
|
2017-01-27 07:23:12 +01:00
|
|
|
|
|
|
|
if (!$type_1->initialized || !$type_2->initialized) {
|
|
|
|
$combined_type->initialized = false;
|
|
|
|
}
|
|
|
|
|
2017-03-19 19:39:05 +01:00
|
|
|
if ($type_1->from_docblock || $type_2->from_docblock) {
|
|
|
|
$combined_type->from_docblock = true;
|
|
|
|
}
|
|
|
|
|
2017-05-10 19:36:05 +02:00
|
|
|
if ($type_1->ignore_nullable_issues || $type_2->ignore_nullable_issues) {
|
|
|
|
$combined_type->ignore_nullable_issues = true;
|
|
|
|
}
|
|
|
|
|
2018-01-25 00:52:58 +01:00
|
|
|
if ($type_1->ignore_falsable_issues || $type_2->ignore_falsable_issues) {
|
|
|
|
$combined_type->ignore_falsable_issues = true;
|
|
|
|
}
|
|
|
|
|
2017-03-13 23:06:56 +01:00
|
|
|
if ($both_failed_reconciliation) {
|
|
|
|
$combined_type->failed_reconciliation = true;
|
|
|
|
}
|
|
|
|
|
2018-03-21 15:46:21 +01:00
|
|
|
if ($type_1->possibly_undefined && $type_2->possibly_undefined) {
|
2018-03-17 21:53:11 +01:00
|
|
|
$combined_type->possibly_undefined = true;
|
|
|
|
}
|
|
|
|
|
2017-01-27 07:23:12 +01:00
|
|
|
return $combined_type;
|
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
|
2016-11-02 07:29:00 +01:00
|
|
|
* - 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>`
|
2016-06-16 02:16:40 +02:00
|
|
|
*
|
|
|
|
* @param array<Atomic> $types
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-06-16 02:16:40 +02:00
|
|
|
* @return Union
|
2016-12-25 02:08:58 +01:00
|
|
|
* @psalm-suppress TypeCoercion
|
2016-06-16 02:16:40 +02:00
|
|
|
*/
|
2016-09-21 03:45:49 +02:00
|
|
|
public static function combineTypes(array $types)
|
2016-06-16 02:16:40 +02:00
|
|
|
{
|
2017-05-27 02:05:57 +02:00
|
|
|
if (in_array(null, $types, true)) {
|
2016-06-16 02:16:40 +02:00
|
|
|
return Type::getMixed();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count($types) === 1) {
|
2018-01-07 22:11:51 +01:00
|
|
|
$union_type = new Union([$types[0]]);
|
|
|
|
|
|
|
|
if ($types[0]->from_docblock) {
|
|
|
|
$union_type->from_docblock = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $union_type;
|
2016-06-16 02:16:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!$types) {
|
|
|
|
throw new \InvalidArgumentException('You must pass at least one type to combineTypes');
|
|
|
|
}
|
|
|
|
|
2017-06-29 17:18:02 +02:00
|
|
|
$combination = new TypeCombination();
|
2016-06-16 02:16:40 +02:00
|
|
|
|
2018-01-07 22:11:51 +01:00
|
|
|
$from_docblock = false;
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
foreach ($types as $type) {
|
2018-01-07 22:11:51 +01:00
|
|
|
$from_docblock = $from_docblock || $type->from_docblock;
|
|
|
|
|
2017-06-29 17:18:02 +02:00
|
|
|
$result = self::scrapeTypeProperties($type, $combination);
|
2016-10-28 19:24:06 +02:00
|
|
|
|
2016-11-05 03:11:46 +01:00
|
|
|
if ($result) {
|
2018-01-07 22:11:51 +01:00
|
|
|
if ($from_docblock) {
|
|
|
|
$result->from_docblock = true;
|
|
|
|
}
|
|
|
|
|
2016-11-05 03:11:46 +01:00
|
|
|
return $result;
|
2016-09-09 22:21:49 +02:00
|
|
|
}
|
2016-06-16 02:16:40 +02:00
|
|
|
}
|
|
|
|
|
2017-06-29 23:40:25 +02:00
|
|
|
if (count($combination->value_types) === 1
|
|
|
|
&& !count($combination->objectlike_entries)
|
|
|
|
&& !count($combination->type_params)
|
|
|
|
) {
|
2017-12-19 06:06:05 +01:00
|
|
|
if (isset($combination->value_types['false'])) {
|
2018-01-07 22:11:51 +01:00
|
|
|
$union_type = Type::getFalse();
|
|
|
|
|
|
|
|
if ($from_docblock) {
|
|
|
|
$union_type->from_docblock = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $union_type;
|
2017-12-19 06:06:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($combination->value_types['true'])) {
|
2018-01-07 22:11:51 +01:00
|
|
|
$union_type = Type::getTrue();
|
|
|
|
|
|
|
|
if ($from_docblock) {
|
|
|
|
$union_type->from_docblock = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $union_type;
|
2016-06-16 07:19:52 +02:00
|
|
|
}
|
2017-06-29 17:18:02 +02:00
|
|
|
} elseif (isset($combination->value_types['void'])) {
|
|
|
|
unset($combination->value_types['void']);
|
2016-11-13 05:59:31 +01:00
|
|
|
|
2018-01-23 18:03:18 +01:00
|
|
|
// if we're merging with another type, we cannot represent it in PHP
|
|
|
|
$from_docblock = true;
|
|
|
|
|
2017-06-29 17:18:02 +02:00
|
|
|
if (!isset($combination->value_types['null'])) {
|
2017-06-29 23:40:25 +02:00
|
|
|
$combination->value_types['null'] = new TNull();
|
2016-11-13 05:59:31 +01:00
|
|
|
}
|
2016-06-16 07:19:52 +02:00
|
|
|
}
|
|
|
|
|
2017-12-09 21:51:38 +01:00
|
|
|
if (isset($combination->value_types['true']) && isset($combination->value_types['false'])) {
|
|
|
|
unset($combination->value_types['true'], $combination->value_types['false']);
|
|
|
|
|
|
|
|
$combination->value_types['bool'] = new TBool();
|
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
$new_types = [];
|
|
|
|
|
2017-06-29 17:48:00 +02:00
|
|
|
if (count($combination->objectlike_entries) &&
|
2017-06-29 23:40:25 +02:00
|
|
|
(!isset($combination->type_params['array'])
|
|
|
|
|| $combination->type_params['array'][1]->isEmpty())
|
2017-06-29 17:48:00 +02:00
|
|
|
) {
|
|
|
|
$new_types[] = new ObjectLike($combination->objectlike_entries);
|
2016-09-10 00:36:35 +02:00
|
|
|
|
2016-10-03 01:33:46 +02:00
|
|
|
// if we're merging an empty array with an object-like, clobber empty array
|
2017-06-29 23:40:25 +02:00
|
|
|
unset($combination->type_params['array']);
|
|
|
|
}
|
2016-06-15 01:22:29 +02:00
|
|
|
|
2017-06-29 23:40:25 +02:00
|
|
|
foreach ($combination->type_params as $generic_type => $generic_type_params) {
|
2017-01-15 01:06:58 +01:00
|
|
|
if ($generic_type === 'array') {
|
2017-11-17 22:33:36 +01:00
|
|
|
if ($combination->objectlike_entries) {
|
2018-03-17 21:53:11 +01:00
|
|
|
$objectlike_generic_type = null;
|
2017-11-17 22:33:36 +01:00
|
|
|
|
2017-12-19 00:47:17 +01:00
|
|
|
$objectlike_keys = [];
|
|
|
|
|
|
|
|
foreach ($combination->objectlike_entries as $property_name => $property_type) {
|
2018-03-17 21:53:11 +01:00
|
|
|
if ($objectlike_generic_type) {
|
|
|
|
$objectlike_generic_type = Type::combineUnionTypes(
|
2017-11-17 22:33:36 +01:00
|
|
|
$property_type,
|
2018-03-17 21:53:11 +01:00
|
|
|
$objectlike_generic_type
|
2017-11-17 22:33:36 +01:00
|
|
|
);
|
|
|
|
} else {
|
2018-03-17 21:53:11 +01:00
|
|
|
$objectlike_generic_type = clone $property_type;
|
2017-11-17 22:33:36 +01:00
|
|
|
}
|
2017-12-19 00:47:17 +01:00
|
|
|
|
|
|
|
if (is_int($property_name)) {
|
|
|
|
if (!isset($objectlike_keys['int'])) {
|
|
|
|
$objectlike_keys['int'] = new TInt;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!isset($objectlike_keys['string'])) {
|
|
|
|
$objectlike_keys['string'] = new TString;
|
|
|
|
}
|
|
|
|
}
|
2017-11-17 22:33:36 +01:00
|
|
|
}
|
|
|
|
|
2018-03-17 21:53:11 +01:00
|
|
|
if (!$objectlike_generic_type) {
|
2017-11-17 22:33:36 +01:00
|
|
|
throw new \InvalidArgumentException('Cannot be null');
|
|
|
|
}
|
|
|
|
|
2018-03-17 21:53:11 +01:00
|
|
|
$objectlike_generic_type->possibly_undefined = false;
|
|
|
|
|
2017-12-19 00:47:17 +01:00
|
|
|
$objectlike_key_type = new Type\Union(array_values($objectlike_keys));
|
|
|
|
|
2017-11-17 22:33:36 +01:00
|
|
|
$generic_type_params[0] = Type::combineUnionTypes(
|
|
|
|
$generic_type_params[0],
|
2017-12-19 00:47:17 +01:00
|
|
|
$objectlike_key_type
|
2017-11-17 22:33:36 +01:00
|
|
|
);
|
|
|
|
$generic_type_params[1] = Type::combineUnionTypes(
|
|
|
|
$generic_type_params[1],
|
2018-03-17 21:53:11 +01:00
|
|
|
$objectlike_generic_type
|
2017-11-17 22:33:36 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:06:58 +01:00
|
|
|
$new_types[] = new TArray($generic_type_params);
|
2017-06-29 23:40:25 +02:00
|
|
|
} elseif (!isset($combination->value_types[$generic_type])) {
|
2017-01-15 01:06:58 +01:00
|
|
|
$new_types[] = new TGenericObject($generic_type, $generic_type_params);
|
|
|
|
}
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
|
|
|
|
2017-11-09 05:14:27 +01:00
|
|
|
foreach ($combination->value_types as $type) {
|
2017-06-29 23:40:25 +02:00
|
|
|
if (!($type instanceof TEmpty)
|
|
|
|
|| (count($combination->value_types) === 1
|
|
|
|
&& !count($new_types))
|
|
|
|
) {
|
|
|
|
$new_types[] = $type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 02:16:40 +02:00
|
|
|
$new_types = array_values($new_types);
|
2016-10-28 19:24:06 +02:00
|
|
|
|
2018-01-07 22:11:51 +01:00
|
|
|
$union_type = new Union($new_types);
|
|
|
|
|
|
|
|
if ($from_docblock) {
|
|
|
|
$union_type->from_docblock = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $union_type;
|
2016-06-15 01:22:29 +02:00
|
|
|
}
|
2016-11-05 03:11:46 +01:00
|
|
|
|
|
|
|
/**
|
2017-06-29 17:18:02 +02:00
|
|
|
* @param Atomic $type
|
|
|
|
* @param TypeCombination $combination
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-05 03:11:46 +01:00
|
|
|
* @return null|Union
|
|
|
|
*/
|
2017-06-29 17:18:02 +02:00
|
|
|
public static function scrapeTypeProperties(Atomic $type, TypeCombination $combination)
|
2016-11-05 03:11:46 +01:00
|
|
|
{
|
2017-01-15 01:06:58 +01:00
|
|
|
if ($type instanceof TMixed) {
|
2016-11-05 03:11:46 +01:00
|
|
|
return Type::getMixed();
|
|
|
|
}
|
|
|
|
|
|
|
|
// deal with false|bool => bool
|
2017-12-09 20:53:39 +01:00
|
|
|
if (($type instanceof TFalse || $type instanceof TTrue) && isset($combination->value_types['bool'])) {
|
2016-11-05 03:11:46 +01:00
|
|
|
return null;
|
2017-12-09 20:53:39 +01:00
|
|
|
}
|
|
|
|
|
2017-12-09 21:51:38 +01:00
|
|
|
if (get_class($type) === 'Psalm\\Type\\Atomic\\TBool' && isset($combination->value_types['false'])) {
|
2017-06-29 17:18:02 +02:00
|
|
|
unset($combination->value_types['false']);
|
2016-11-05 03:11:46 +01:00
|
|
|
}
|
|
|
|
|
2017-12-09 21:51:38 +01:00
|
|
|
if (get_class($type) === 'Psalm\\Type\\Atomic\\TBool' && isset($combination->value_types['true'])) {
|
2017-12-09 20:53:39 +01:00
|
|
|
unset($combination->value_types['true']);
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:06:58 +01:00
|
|
|
$type_key = $type->getKey();
|
|
|
|
|
|
|
|
if ($type instanceof TArray || $type instanceof TGenericObject) {
|
2018-01-28 23:26:09 +01:00
|
|
|
foreach ($type->type_params as $i => $type_param) {
|
2017-06-29 23:40:25 +02:00
|
|
|
if (isset($combination->type_params[$type_key][$i])) {
|
|
|
|
$combination->type_params[$type_key][$i] = Type::combineUnionTypes(
|
|
|
|
$combination->type_params[$type_key][$i],
|
|
|
|
$type_param
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$combination->type_params[$type_key][$i] = $type_param;
|
|
|
|
}
|
2016-11-05 03:11:46 +01:00
|
|
|
}
|
|
|
|
} elseif ($type instanceof ObjectLike) {
|
2018-03-17 21:53:11 +01:00
|
|
|
$existing_objectlike_entries = (bool) $combination->objectlike_entries;
|
|
|
|
$possibly_undefined_entries = $combination->objectlike_entries;
|
|
|
|
|
2016-11-05 03:11:46 +01:00
|
|
|
foreach ($type->properties as $candidate_property_name => $candidate_property_type) {
|
2017-06-29 17:48:00 +02:00
|
|
|
$value_type = isset($combination->objectlike_entries[$candidate_property_name])
|
|
|
|
? $combination->objectlike_entries[$candidate_property_name]
|
2016-11-13 17:54:40 +01:00
|
|
|
: null;
|
|
|
|
|
|
|
|
if (!$value_type) {
|
2018-03-17 21:53:11 +01:00
|
|
|
$combination->objectlike_entries[$candidate_property_name] = clone $candidate_property_type;
|
|
|
|
// it's possibly undefined if there are existing objectlike entries
|
|
|
|
$combination->objectlike_entries[$candidate_property_name]->possibly_undefined
|
|
|
|
= $existing_objectlike_entries;
|
2016-11-05 03:11:46 +01:00
|
|
|
} else {
|
2017-06-29 17:48:00 +02:00
|
|
|
$combination->objectlike_entries[$candidate_property_name] = Type::combineUnionTypes(
|
2016-11-13 17:54:40 +01:00
|
|
|
$value_type,
|
2016-11-05 03:11:46 +01:00
|
|
|
$candidate_property_type
|
|
|
|
);
|
|
|
|
}
|
2018-03-17 21:53:11 +01:00
|
|
|
|
|
|
|
unset($possibly_undefined_entries[$candidate_property_name]);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($possibly_undefined_entries as $type) {
|
|
|
|
$type->possibly_undefined = true;
|
2016-11-05 03:11:46 +01:00
|
|
|
}
|
|
|
|
} else {
|
2017-06-29 23:40:25 +02:00
|
|
|
$combination->value_types[$type_key] = $type;
|
2016-11-05 03:11:46 +01:00
|
|
|
}
|
|
|
|
}
|
2016-06-14 07:23:57 +02:00
|
|
|
}
|