1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-12 09:19:40 +01:00
psalm/src/Psalm/Type.php

978 lines
28 KiB
PHP
Raw Normal View History

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;
use Psalm\Storage\FunctionLikeParameter;
2016-07-26 00:37:44 +02:00
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\ObjectLike;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TBool;
use Psalm\Type\Atomic\TCallable;
use Psalm\Type\Atomic\TClassString;
use Psalm\Type\Atomic\TEmpty;
use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TGenericObject;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TLiteralClassString;
use Psalm\Type\Atomic\TLiteralFloat;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralString;
use Psalm\Type\Atomic\TMixed;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TNull;
use Psalm\Type\Atomic\TNumeric;
use Psalm\Type\Atomic\TObject;
use Psalm\Type\Atomic\TResource;
use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTrue;
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
{
/**
* @var array<string, bool>
*/
public static $PSALM_RESERVED_WORDS = [
'int' => true,
'string' => true,
'float' => true,
'bool' => true,
'false' => true,
'true' => true,
'object' => true,
'empty' => true,
'callable' => true,
'array' => true,
'iterable' => true,
'null' => true,
'mixed' => true,
'numeric-string' => true,
'class-string' => true,
'boolean' => true,
'integer' => true,
'double' => true,
'real' => true,
'resource' => true,
'void' => true,
'self' => true,
'static' => true,
'scalar' => true,
2018-04-04 20:42:23 +02:00
'numeric' => true,
];
/**
2018-02-22 00:59:31 +01:00
* @var array<string, array<int, string>>
*/
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
*
* @param string $type_string
* @param bool $php_compatible
* @param array<string, string> $template_type_names
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
*/
public static function parseString($type_string, $php_compatible = false, array $template_type_names = [])
2016-06-14 07:23:57 +02:00
{
return self::parseTokens(self::tokenize($type_string), $php_compatible, $template_type_names);
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
*
* @param array<int, string> $type_tokens
* @param bool $php_compatible
* @param array<string, string> $template_type_names
2018-05-20 23:19:53 +02:00
*
* @return Union
*/
public static function parseTokens(array $type_tokens, $php_compatible = false, array $template_type_names = [])
2018-05-20 23:19:53 +02:00
{
2016-06-14 07:23:57 +02:00
if (count($type_tokens) === 1) {
$only_token = $type_tokens[0];
2016-06-24 00:45:46 +02:00
// Note: valid identifiers can include class names or $this
2018-05-20 23:19:53 +02:00
if (!preg_match('@^(\$this|\\\\?[a-zA-Z_\x7f-\xff][\\\\\-0-9a-zA-Z_\x7f-\xff]*)$@', $only_token)) {
throw new TypeParseTreeException("Invalid type '$only_token'");
}
$only_token = self::fixScalarTerms($only_token, $php_compatible);
return new Union([Atomic::create($only_token, $php_compatible, $template_type_names)]);
2016-06-14 07:23:57 +02:00
}
2016-11-21 05:31:10 +01:00
try {
$parse_tree = ParseTree::createFromTokens($type_tokens);
$parsed_type = self::getTypeFromTree($parse_tree, $php_compatible, $template_type_names);
} 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
* @param bool $php_compatible
2017-05-27 02:16:18 +02:00
*
2016-10-15 06:12:57 +02:00
* @return string
*/
private static function fixScalarTerms($type_string, $php_compatible = false)
{
$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
}
return $type_string;
}
2016-11-02 07:29:00 +01:00
/**
* @param ParseTree $parse_tree
* @param bool $php_compatible
* @param array<string, string> $template_type_names
2017-05-27 02:16:18 +02:00
*
* @return Atomic|TArray|TGenericObject|ObjectLike|Union
2016-11-02 07:29:00 +01:00
*/
public static function getTypeFromTree(
ParseTree $parse_tree,
$php_compatible = false,
array $template_type_names = []
) {
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
*/
function (ParseTree $child_tree) use ($template_type_names) {
$tree_type = self::getTypeFromTree($child_tree, false, $template_type_names);
2017-05-25 04:07:49 +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
) {
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') {
return new TArray($generic_params);
2016-09-22 04:15:46 +02: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) {
$atomic_type = self::getTypeFromTree($child_tree->children[0], false, $template_type_names);
2018-06-09 05:54:07 +02:00
$has_null = true;
} else {
$atomic_type = self::getTypeFromTree($child_tree, false, $template_type_names);
2018-06-09 05:54:07 +02:00
}
2016-12-07 20:13:39 +01:00
if ($atomic_type instanceof Union) {
foreach ($atomic_type->getTypes() as $type) {
$atomic_types[] = $type;
}
continue;
}
2018-06-09 05:54:07 +02:00
$atomic_types[] = $atomic_type;
}
if ($has_null) {
$atomic_types[] = new TNull;
}
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) {
$intersection_types = array_map(
/**
* @return Atomic
*/
function (ParseTree $child_tree) use ($template_type_names) {
$atomic_type = self::getTypeFromTree($child_tree, false, $template_type_names);
if (!$atomic_type instanceof Atomic) {
2018-03-23 03:28:06 +01:00
throw new TypeParseTreeException(
'Intersection types cannot contain unions'
);
}
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;
2018-03-22 22:55:36 +01:00
return $first_type;
}
2018-03-21 01:19:26 +01:00
if ($parse_tree instanceof ParseTree\ObjectLikeTree) {
$properties = [];
2018-03-21 01:19:26 +01:00
$type = $parse_tree->value;
foreach ($parse_tree->children as $i => $property_branch) {
2018-03-21 01:19:26 +01:00
if (!$property_branch instanceof ParseTree\ObjectLikePropertyTree) {
$property_type = self::getTypeFromTree($property_branch, false, $template_type_names);
$property_maybe_undefined = false;
$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, $template_type_names);
$property_maybe_undefined = $property_branch->possibly_undefined;
2018-03-21 01:19:26 +01:00
$property_key = $property_branch->value;
} else {
2018-03-21 01:19:26 +01:00
throw new \InvalidArgumentException(
'Unexpected number of property parts (' . count($property_branch->children) . ')'
);
}
2016-10-03 00:59:16 +02:00
if (!$property_type instanceof Union) {
$property_type = new Union([$property_type]);
}
if ($property_maybe_undefined) {
$property_type->possibly_undefined = true;
}
$properties[$property_key] = $property_type;
}
2018-03-21 01:19:26 +01:00
if ($type !== 'array') {
throw new \InvalidArgumentException('Object-like type must be array');
2016-10-30 17:46:18 +01:00
}
if (!$properties) {
throw new \InvalidArgumentException('No properties supplied for ObjectLike');
}
return new ObjectLike($properties);
}
if ($parse_tree instanceof ParseTree\CallableWithReturnTypeTree) {
$callable_type = self::getTypeFromTree($parse_tree->children[0], false, $template_type_names);
if (!$callable_type instanceof TCallable && !$callable_type instanceof Type\Atomic\Fn) {
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
}
$return_type = self::getTypeFromTree($parse_tree->children[1], false, $template_type_names);
$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
*/
function (ParseTree $child_tree) use ($template_type_names) {
$is_variadic = false;
$is_optional = false;
if ($child_tree instanceof ParseTree\CallableParamTree) {
$tree_type = self::getTypeFromTree($child_tree->children[0], false, $template_type_names);
$is_variadic = $child_tree->variadic;
$is_optional = $child_tree->has_default;
} else {
$tree_type = self::getTypeFromTree($child_tree, false, $template_type_names);
}
$tree_type = $tree_type instanceof Union ? $tree_type : new Union([$tree_type]);
return new FunctionLikeParameter(
'',
false,
$tree_type,
null,
null,
$is_optional,
false,
$is_variadic
);
},
$parse_tree->children
);
if (in_array(strtolower($parse_tree->value), ['closure', '\closure'], true)) {
return new Type\Atomic\Fn('Closure', $params);
}
return new TCallable($parse_tree->value, $params);
}
2018-03-23 03:28:06 +01:00
if ($parse_tree instanceof ParseTree\EncapsulationTree) {
return self::getTypeFromTree($parse_tree->children[0], false, $template_type_names);
2018-03-23 03:28:06 +01:00
}
2018-06-09 05:54:07 +02:00
if ($parse_tree instanceof ParseTree\NullableTree) {
$atomic_type = self::getTypeFromTree($parse_tree->children[0], false, $template_type_names);
2018-06-09 05:54:07 +02:00
if (!$atomic_type instanceof Atomic) {
throw new \UnexpectedValueException(
'Was expecting an atomic type, got ' . get_class($atomic_type)
);
}
return TypeCombination::combineTypes([
new TNull,
$atomic_type
]);
}
2018-03-21 01:19:26 +01:00
if (!$parse_tree instanceof ParseTree\Value) {
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));
}
if (strpos($parse_tree->value, '::')) {
list($fq_classlike_name, $const_name) = explode('::', $parse_tree->value);
return new Atomic\TScalarClassConstant($fq_classlike_name, $const_name);
}
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 . '\'');
}
$atomic_type = self::fixScalarTerms($parse_tree->value, $php_compatible);
2016-09-09 22:21:49 +02:00
return Atomic::create($atomic_type, $php_compatible, $template_type_names);
}
/**
2018-05-20 23:19:53 +02:00
* @param string $string_type
* @param bool $ignore_space
2017-05-27 02:16:18 +02:00
*
2016-10-03 17:38:59 +02:00
* @return array<int,string>
*/
2018-05-20 23:19:53 +02:00
public static function tokenize($string_type, $ignore_space = true)
{
2018-05-20 23:19:53 +02:00
$type_tokens = [''];
$was_char = false;
$quote_char = null;
$escaped = false;
2018-05-20 23:19:53 +02:00
if (isset(self::$memoized_tokens[$string_type])) {
return self::$memoized_tokens[$string_type];
}
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);
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) {
continue;
}
if ($was_char) {
2018-05-20 23:19:53 +02:00
$type_tokens[++$rtc] = '';
}
if ($quote_char) {
if ($char === $quote_char && $i > 1 && !$escaped) {
$quote_char = null;
$type_tokens[$rtc] .= $char;
$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;
$type_tokens[$rtc] .= $char;
continue;
}
if ($char === '"' || $char === '\'') {
if ($type_tokens[$rtc] === '') {
$type_tokens[$rtc] = $char;
} else {
$type_tokens[++$rtc] = $char;
}
$quote_char = $char;
$was_char = false;
continue;
}
if ($char === '<'
|| $char === '>'
|| $char === '|'
|| $char === '?'
|| $char === ','
|| $char === '{'
|| $char === '}'
|| $char === '['
|| $char === ']'
|| $char === '('
|| $char === ')'
|| $char === ' '
|| $char === '&'
|| $char === '='
2016-11-02 07:29:00 +01:00
) {
2018-05-20 23:19:53 +02:00
if ($type_tokens[$rtc] === '') {
$type_tokens[$rtc] = $char;
2016-11-02 07:29:00 +01:00
} else {
2018-05-20 23:19:53 +02:00
$type_tokens[++$rtc] = $char;
}
$was_char = true;
2018-05-20 23:19:53 +02:00
continue;
}
if ($char === ':') {
if ($i + 1 < $c && $chars[$i + 1] === ':') {
if ($type_tokens[$rtc] === '') {
$type_tokens[$rtc] = '::';
} else {
$type_tokens[++$rtc] = '::';
}
$was_char = true;
$i++;
continue;
}
if ($type_tokens[$rtc] === '') {
$type_tokens[$rtc] = ':';
} else {
$type_tokens[++$rtc] = ':';
}
$was_char = true;
continue;
}
2018-05-20 23:19:53 +02:00
if ($char === '.') {
2018-03-27 20:43:39 +02:00
if ($i + 2 > $c || $chars[$i + 1] !== '.' || $chars[$i + 2] !== '.') {
throw new TypeParseTreeException('Unexpected token ' . $char);
}
2018-05-20 23:19:53 +02:00
if ($type_tokens[$rtc] === '') {
$type_tokens[$rtc] = '...';
} else {
2018-05-20 23:19:53 +02:00
$type_tokens[++$rtc] = '...';
}
$was_char = true;
$i += 2;
2018-05-20 23:19:53 +02:00
continue;
}
2018-05-20 23:19:53 +02:00
$type_tokens[$rtc] .= $char;
$was_char = false;
}
2018-05-20 23:19:53 +02:00
self::$memoized_tokens[$string_type] = $type_tokens;
2018-05-20 23:19:53 +02:00
return $type_tokens;
}
/**
2018-05-20 23:19:53 +02:00
* @param string $string_type
* @param Aliases $aliases
* @param array<string, string>|null $template_type_names
2018-07-15 23:23:17 +02:00
* @param array<string, array<int, string>>|null $type_aliases
*
2018-05-20 23:19:53 +02:00
* @return array<int, string>
*/
public static function fixUpLocalType(
2018-05-20 23:19:53 +02:00
$string_type,
Aliases $aliases,
2018-07-15 23:23:17 +02:00
array $template_type_names = null,
array $type_aliases = null
) {
2018-05-20 23:19:53 +02:00
$type_tokens = self::tokenize($string_type);
2018-05-20 23:19:53 +02:00
for ($i = 0, $l = count($type_tokens); $i < $l; $i++) {
$string_type_token = $type_tokens[$i];
2018-05-03 19:15:16 +02:00
if (in_array(
2018-05-20 23:19:53 +02:00
$string_type_token,
['<', '>', '|', '?', ',', '{', '}', ':', '::', '[', ']', '(', ')', '&'],
2018-05-03 19:15:16 +02:00
true
)) {
continue;
}
2018-05-20 23:19:53 +02:00
if ($string_type_token[0] === '"'
|| $string_type_token[0] === '\''
2018-06-14 21:58:49 +02:00
|| $string_type_token === '0'
2018-05-20 23:19:53 +02:00
|| preg_match('/[1-9]/', $string_type_token[0])
) {
continue;
}
if (isset($type_tokens[$i + 1]) && $type_tokens[$i + 1] === ':') {
continue;
}
if ($i > 0 && $type_tokens[$i - 1] === '::') {
continue;
}
2018-05-20 23:19:53 +02:00
$type_tokens[$i] = $string_type_token = self::fixScalarTerms($string_type_token);
2018-05-20 23:19:53 +02:00
if (isset(self::$PSALM_RESERVED_WORDS[$string_type_token])) {
continue;
}
if (isset($template_type_names[$string_type_token])) {
continue;
}
2018-05-20 23:19:53 +02:00
if (isset($type_tokens[$i + 1])) {
$next_char = $type_tokens[$i + 1];
if ($next_char === ':') {
continue;
}
2018-05-20 23:19:53 +02:00
if ($next_char === '?' && isset($type_tokens[$i + 2]) && $type_tokens[$i + 2] === ':') {
continue;
}
}
2018-05-20 23:19:53 +02:00
if ($string_type_token[0] === '$') {
continue;
}
2018-07-15 23:23:17 +02:00
if (isset($type_aliases[$string_type_token])) {
$replacement_tokens = $type_aliases[$string_type_token];
$diff = count($replacement_tokens) - 1;
for ($j = 0; $j < $diff + 1; $j++) {
$type_tokens[$i + $j] = $replacement_tokens[$j];
}
$i += $diff;
$l += $diff;
} else {
$type_tokens[$i] = self::getFQCLNFromString(
$string_type_token,
$aliases
);
}
}
2018-05-20 23:19:53 +02:00
return $type_tokens;
}
/**
* @param string $class
* @param Aliases $aliases
*
* @return string
*/
public static function getFQCLNFromString($class, Aliases $aliases)
{
2018-06-14 19:49:16 +02:00
if ($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-11-02 07:29:00 +01:00
/**
* @param bool $from_calculation
* @param int|null $value
*
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
{
if ($value !== null) {
$union = new Union([new TLiteralInt($value)]);
} else {
$union = new Union([new TInt()]);
}
$union->from_calculation = $from_calculation;
2016-06-15 01:22:29 +02:00
return $union;
2016-06-15 01:22:29 +02:00
}
/**
* @return Type\Union
*/
public static function getNumeric()
{
$type = new TNumeric;
return new Union([$type]);
}
2016-11-02 07:29:00 +01:00
/**
* @param string|null $value
*
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
{
if ($value !== null) {
$type = new TLiteralString($value);
} else {
$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
}
/**
* @param string $class_type
*
* @return Type\Union
*/
public static function getClassString($class_type = null)
{
if (!$class_type) {
return new Union([new TClassString()]);
}
$type = new TLiteralClassString($class_type);
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
{
$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
/**
* @param bool $from_isset
*
2016-11-02 07:29:00 +01:00
* @return Type\Union
*/
public static function getMixed($from_isset = false)
2016-06-15 01:22:29 +02:00
{
$type = new TMixed($from_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
}
/**
* @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
{
$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
/**
* @param float|null $value
*
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
{
if ($value !== null) {
$type = new TLiteralFloat($value);
} 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
{
$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()
{
$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
{
$type = new TArray(
2016-09-09 22:21:49 +02: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()
{
$array_type = new TArray(
[
new Type\Union([new TEmpty]),
new Type\Union([new TEmpty]),
]
);
$array_type->count = 0;
2016-09-12 06:02:26 +02:00
return new Type\Union([
$array_type,
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
{
$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
{
$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
}
/**
* @return Type\Union
*/
public static function getTrue()
{
$type = new TTrue;
return new Union([$type]);
}
/**
* @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
*/
public static function combineUnionTypes(Union $type_1, Union $type_2)
2016-06-15 01:22:29 +02:00
{
2018-06-09 05:54:07 +02:00
if ($type_1->isVanillaMixed() || $type_2->isVanillaMixed()) {
$combined_type = Type::getMixed();
} else {
$both_failed_reconciliation = false;
2017-03-13 23:06:56 +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
}
$combined_type = TypeCombination::combineTypes(
array_merge(
array_values($type_1->getTypes()),
array_values($type_2->getTypes())
)
);
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;
}
2017-03-13 23:06:56 +01:00
}
if ($type_1->possibly_undefined || $type_2->possibly_undefined) {
$combined_type->possibly_undefined = true;
}
return $combined_type;
2016-06-16 02:16:40 +02:00
}
2016-06-14 07:23:57 +02:00
}