mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Add prototype for conditional return type
This commit is contained in:
parent
26694345d6
commit
6058725256
@ -24,6 +24,7 @@ use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\TTemplateParamClass;
|
||||
use Psalm\Type\Atomic\GetClassT;
|
||||
use Psalm\Type\Atomic\GetTypeT;
|
||||
use Psalm\Type\Atomic\TConditional;
|
||||
use Psalm\Type\Atomic\THtmlEscapedString;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TIterable;
|
||||
@ -913,6 +914,28 @@ class TypeAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($container_type_part instanceof TConditional) {
|
||||
$atomic_types = array_merge(
|
||||
array_values($container_type_part->if_type->getAtomicTypes()),
|
||||
array_values($container_type_part->else_type->getAtomicTypes())
|
||||
);
|
||||
|
||||
foreach ($atomic_types as $container_as_type_part) {
|
||||
if (self::isAtomicContainedBy(
|
||||
$codebase,
|
||||
$input_type_part,
|
||||
$container_as_type_part,
|
||||
$allow_interface_equality,
|
||||
$allow_float_int_equality,
|
||||
$atomic_comparison_result
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($input_type_part instanceof TTemplateParam) {
|
||||
if ($input_type_part->extra_types) {
|
||||
foreach ($input_type_part->extra_types as $extra_type) {
|
||||
|
@ -317,6 +317,19 @@ class ParseTree
|
||||
break;
|
||||
}
|
||||
|
||||
while ($current_parent instanceof ParseTree\UnionTree
|
||||
&& $current_leaf->parent
|
||||
) {
|
||||
$current_leaf = $current_leaf->parent;
|
||||
$current_parent = $current_leaf->parent;
|
||||
}
|
||||
|
||||
if ($current_parent && $current_parent instanceof ParseTree\ConditionalTree) {
|
||||
$current_leaf = $current_parent;
|
||||
$current_parent = $current_parent->parent;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$current_parent) {
|
||||
throw new TypeParseTreeException('Cannot process colon without parent');
|
||||
}
|
||||
@ -372,22 +385,44 @@ class ParseTree
|
||||
|
||||
case '?':
|
||||
if ($next_token === null || $next_token[0] !== ':') {
|
||||
$new_parent = !$current_leaf instanceof ParseTree\Root ? $current_leaf : null;
|
||||
|
||||
$new_leaf = new ParseTree\NullableTree(
|
||||
$new_parent
|
||||
);
|
||||
|
||||
if ($current_leaf instanceof ParseTree\Root) {
|
||||
$current_leaf = $parse_tree = $new_leaf;
|
||||
break;
|
||||
while (($current_leaf instanceof ParseTree\Value
|
||||
|| $current_leaf instanceof ParseTree\UnionTree)
|
||||
&& $current_leaf->parent
|
||||
) {
|
||||
$current_leaf = $current_leaf->parent;
|
||||
}
|
||||
|
||||
if ($new_leaf->parent) {
|
||||
$new_leaf->parent->children[] = $new_leaf;
|
||||
}
|
||||
if ($current_leaf instanceof ParseTree\TemplateIsTree && $current_leaf->parent) {
|
||||
$current_parent = $current_leaf->parent;
|
||||
|
||||
$current_leaf = $new_leaf;
|
||||
$new_leaf = new ParseTree\ConditionalTree(
|
||||
$current_leaf,
|
||||
$current_leaf->parent
|
||||
);
|
||||
|
||||
$current_leaf->parent = $new_leaf;
|
||||
|
||||
array_pop($current_parent->children);
|
||||
$current_parent->children[] = $new_leaf;
|
||||
$current_leaf = $new_leaf;
|
||||
} else {
|
||||
$new_parent = !$current_leaf instanceof ParseTree\Root ? $current_leaf : null;
|
||||
|
||||
$new_leaf = new ParseTree\NullableTree(
|
||||
$new_parent
|
||||
);
|
||||
|
||||
if ($current_leaf instanceof ParseTree\Root) {
|
||||
$current_leaf = $parse_tree = $new_leaf;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($new_leaf->parent) {
|
||||
$new_leaf->parent->children[] = $new_leaf;
|
||||
}
|
||||
|
||||
$current_leaf = $new_leaf;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
@ -423,9 +458,16 @@ class ParseTree
|
||||
$current_parent = $current_leaf->parent;
|
||||
}
|
||||
|
||||
$new_parent_leaf = new ParseTree\UnionTree($current_parent);
|
||||
$new_parent_leaf->children = [$current_leaf];
|
||||
$current_leaf->parent = $new_parent_leaf;
|
||||
if ($current_parent instanceof ParseTree\TemplateIsTree) {
|
||||
$new_parent_leaf = new ParseTree\UnionTree($current_leaf);
|
||||
$new_parent_leaf->children = [$current_leaf];
|
||||
$new_parent_leaf->parent = $current_parent;
|
||||
$current_leaf->parent = $new_parent_leaf;
|
||||
} else {
|
||||
$new_parent_leaf = new ParseTree\UnionTree($current_parent);
|
||||
$new_parent_leaf->children = [$current_leaf];
|
||||
$current_leaf->parent = $new_parent_leaf;
|
||||
}
|
||||
|
||||
if ($current_parent) {
|
||||
array_pop($current_parent->children);
|
||||
@ -472,32 +514,46 @@ class ParseTree
|
||||
|
||||
break;
|
||||
|
||||
case 'is':
|
||||
case 'as':
|
||||
if ($i > 0) {
|
||||
$current_parent = $current_leaf->parent;
|
||||
|
||||
if (!$current_leaf instanceof ParseTree\Value
|
||||
|| !$current_parent instanceof ParseTree\GenericTree
|
||||
|| !$next_token
|
||||
) {
|
||||
throw new TypeParseTreeException('Unexpected token ' . $type_token[0]);
|
||||
if ($current_parent) {
|
||||
array_pop($current_parent->children);
|
||||
}
|
||||
|
||||
array_pop($current_parent->children);
|
||||
if ($type_token[0] === 'as') {
|
||||
if (!$current_leaf instanceof ParseTree\Value
|
||||
|| !$current_parent instanceof ParseTree\GenericTree
|
||||
|| !$next_token
|
||||
) {
|
||||
throw new TypeParseTreeException('Unexpected token ' . $type_token[0]);
|
||||
}
|
||||
|
||||
$current_leaf = new ParseTree\TemplateAsTree(
|
||||
$current_leaf->value,
|
||||
$next_token[0],
|
||||
$current_parent
|
||||
);
|
||||
$current_leaf = new ParseTree\TemplateAsTree(
|
||||
$current_leaf->value,
|
||||
$next_token[0],
|
||||
$current_parent
|
||||
);
|
||||
|
||||
$current_parent->children[] = $current_leaf;
|
||||
++$i;
|
||||
$current_parent->children[] = $current_leaf;
|
||||
++$i;
|
||||
} elseif ($current_leaf instanceof ParseTree\Value) {
|
||||
$current_leaf = new ParseTree\TemplateIsTree(
|
||||
$current_leaf->value,
|
||||
$current_parent
|
||||
);
|
||||
|
||||
if ($current_parent) {
|
||||
$current_parent->children[] = $current_leaf;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// falling through for methods named 'as'
|
||||
// falling through for methods named 'as' or 'is'
|
||||
|
||||
default:
|
||||
$new_parent = !$current_leaf instanceof ParseTree\Root ? $current_leaf : null;
|
||||
|
19
src/Psalm/Internal/Type/ParseTree/ConditionalTree.php
Normal file
19
src/Psalm/Internal/Type/ParseTree/ConditionalTree.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Type\ParseTree;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ConditionalTree extends \Psalm\Internal\Type\ParseTree
|
||||
{
|
||||
/**
|
||||
* @var TemplateIsTree
|
||||
*/
|
||||
public $condition;
|
||||
|
||||
public function __construct(TemplateIsTree $condition, ?\Psalm\Internal\Type\ParseTree $parent = null)
|
||||
{
|
||||
$this->condition = $condition;
|
||||
$this->parent = $parent;
|
||||
}
|
||||
}
|
19
src/Psalm/Internal/Type/ParseTree/TemplateIsTree.php
Normal file
19
src/Psalm/Internal/Type/ParseTree/TemplateIsTree.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Type\ParseTree;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class TemplateIsTree extends \Psalm\Internal\Type\ParseTree
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $param_name;
|
||||
|
||||
public function __construct(string $param_name, ?\Psalm\Internal\Type\ParseTree $parent = null)
|
||||
{
|
||||
$this->param_name = $param_name;
|
||||
$this->parent = $parent;
|
||||
}
|
||||
}
|
@ -735,6 +735,53 @@ abstract class Type
|
||||
);
|
||||
}
|
||||
|
||||
if ($parse_tree instanceof ParseTree\ConditionalTree) {
|
||||
$template_param_name = $parse_tree->condition->param_name;
|
||||
|
||||
if (isset($template_type_map[$template_param_name])) {
|
||||
$first_class = array_keys($template_type_map[$template_param_name])[0];
|
||||
|
||||
$conditional_type = self::getTypeFromTree(
|
||||
$parse_tree->condition->children[0],
|
||||
null,
|
||||
$template_type_map
|
||||
);
|
||||
|
||||
$if_type = self::getTypeFromTree(
|
||||
$parse_tree->children[0],
|
||||
null,
|
||||
$template_type_map
|
||||
);
|
||||
|
||||
$else_type = self::getTypeFromTree(
|
||||
$parse_tree->children[1],
|
||||
null,
|
||||
$template_type_map
|
||||
);
|
||||
|
||||
if ($conditional_type instanceof Type\Atomic) {
|
||||
$conditional_type = new Type\Union([$conditional_type]);
|
||||
}
|
||||
|
||||
if ($if_type instanceof Type\Atomic) {
|
||||
$if_type = new Type\Union([$if_type]);
|
||||
}
|
||||
|
||||
if ($else_type instanceof Type\Atomic) {
|
||||
$else_type = new Type\Union([$else_type]);
|
||||
}
|
||||
|
||||
return new Atomic\TConditional(
|
||||
$template_param_name,
|
||||
$first_class,
|
||||
$template_type_map[$template_param_name][$first_class][0],
|
||||
$conditional_type,
|
||||
$if_type,
|
||||
$else_type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$parse_tree instanceof ParseTree\Value) {
|
||||
throw new \InvalidArgumentException('Unrecognised parse tree type ' . get_class($parse_tree));
|
||||
}
|
||||
@ -905,11 +952,11 @@ abstract class Type
|
||||
$type_tokens[++$rtc] = [' ', $i - 1];
|
||||
$type_tokens[++$rtc] = ['', $i];
|
||||
} elseif ($was_space
|
||||
&& $char === 'a'
|
||||
&& ($char === 'a' || $char === 'i')
|
||||
&& ($chars[$i + 1] ?? null) === 's'
|
||||
&& ($chars[$i + 2] ?? null) === ' '
|
||||
) {
|
||||
$type_tokens[++$rtc] = ['as', $i - 1];
|
||||
$type_tokens[++$rtc] = [$char . 's', $i - 1];
|
||||
$type_tokens[++$rtc] = ['', ++$i];
|
||||
continue;
|
||||
} elseif ($was_char) {
|
||||
@ -1079,7 +1126,7 @@ abstract class Type
|
||||
if (in_array(
|
||||
$string_type_token[0],
|
||||
[
|
||||
'<', '>', '|', '?', ',', '{', '}', ':', '::', '[', ']', '(', ')', '&', '=', '...', 'as',
|
||||
'<', '>', '|', '?', ',', '{', '}', ':', '::', '[', ']', '(', ')', '&', '=', '...', 'as', 'is',
|
||||
],
|
||||
true
|
||||
)) {
|
||||
|
161
src/Psalm/Type/Atomic/TConditional.php
Normal file
161
src/Psalm/Type/Atomic/TConditional.php
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
use function implode;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\StatementsSource;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Union;
|
||||
use Psalm\Storage\MethodStorage;
|
||||
use function array_map;
|
||||
use function strtolower;
|
||||
|
||||
class TConditional extends \Psalm\Type\Atomic
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $param_name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $defining_class;
|
||||
|
||||
/**
|
||||
* @var Union
|
||||
*/
|
||||
public $as_type;
|
||||
|
||||
/**
|
||||
* @var Union
|
||||
*/
|
||||
public $conditional_type;
|
||||
|
||||
/**
|
||||
* @var Union
|
||||
*/
|
||||
public $if_type;
|
||||
|
||||
/**
|
||||
* @var Union
|
||||
*/
|
||||
public $else_type;
|
||||
|
||||
/**
|
||||
* @param string $defining_class
|
||||
*/
|
||||
public function __construct(
|
||||
string $param_name,
|
||||
string $defining_class,
|
||||
Union $as_type,
|
||||
Union $conditional_type,
|
||||
Union $if_type,
|
||||
Union $else_type
|
||||
) {
|
||||
$this->param_name = $param_name;
|
||||
$this->defining_class = $defining_class;
|
||||
$this->as_type = $as_type;
|
||||
$this->conditional_type = $conditional_type;
|
||||
$this->if_type = $if_type;
|
||||
$this->else_type = $else_type;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return '('
|
||||
. $this->param_name
|
||||
. ' is ' . $this->conditional_type
|
||||
. ' ? ' . $this->if_type
|
||||
. ' : ' . $this->else_type
|
||||
. ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey(bool $include_extra = true)
|
||||
{
|
||||
return $this->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAssertionString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return '('
|
||||
. $this->param_name . ':' . $this->defining_class
|
||||
. ' is ' . $this->conditional_type->getId()
|
||||
. ' ? ' . $this->if_type->getId()
|
||||
. ' : ' . $this->else_type->getId()
|
||||
. ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $namespace
|
||||
* @param array<string> $aliased_classes
|
||||
* @param string|null $this_class
|
||||
* @param int $php_major_version
|
||||
* @param int $php_minor_version
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function toPhpString(
|
||||
$namespace,
|
||||
array $aliased_classes,
|
||||
$this_class,
|
||||
$php_major_version,
|
||||
$php_minor_version
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $namespace
|
||||
* @param array<string, string> $aliased_classes
|
||||
* @param string|null $this_class
|
||||
* @param bool $use_phpdoc_format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toNamespacedString(
|
||||
?string $namespace,
|
||||
array $aliased_classes,
|
||||
?string $this_class,
|
||||
bool $use_phpdoc_format
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getChildNodes() : array
|
||||
{
|
||||
return [$this->conditional_type, $this->if_type, $this->else_type];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function canBeFullyExpressedInPhp()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array{Type\Union, 1?:int}>> $template_types
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function replaceTemplateTypesWithArgTypes(array $template_types, ?Codebase $codebase)
|
||||
{
|
||||
$this->conditional_type->replaceTemplateTypesWithArgTypes($template_types, $codebase);
|
||||
$this->if_type->replaceTemplateTypesWithArgTypes($template_types, $codebase);
|
||||
$this->else_type->replaceTemplateTypesWithArgTypes($template_types, $codebase);
|
||||
}
|
||||
}
|
@ -1267,6 +1267,50 @@ class Union implements TypeNode
|
||||
} else {
|
||||
$new_types[$key] = new Type\Atomic\TMixed();
|
||||
}
|
||||
} elseif ($atomic_type instanceof Type\Atomic\TConditional
|
||||
&& $codebase
|
||||
) {
|
||||
$template_type = isset($template_types[$atomic_type->param_name][$atomic_type->defining_class])
|
||||
? clone $template_types[$atomic_type->param_name][$atomic_type->defining_class][0]
|
||||
: null;
|
||||
|
||||
$class_template_type = null;
|
||||
|
||||
if ($template_type) {
|
||||
if (TypeAnalyzer::isContainedBy(
|
||||
$codebase,
|
||||
$template_type,
|
||||
$atomic_type->conditional_type
|
||||
)) {
|
||||
$class_template_type = clone $atomic_type->if_type;
|
||||
} elseif (TypeAnalyzer::isContainedBy(
|
||||
$codebase,
|
||||
$template_type,
|
||||
$atomic_type->as_type
|
||||
)
|
||||
&& !TypeAnalyzer::isContainedBy(
|
||||
$codebase,
|
||||
$atomic_type->as_type,
|
||||
$template_type
|
||||
)
|
||||
) {
|
||||
$class_template_type = clone $atomic_type->else_type;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$class_template_type) {
|
||||
$class_template_type = Type::combineUnionTypes(
|
||||
$atomic_type->if_type,
|
||||
$atomic_type->else_type,
|
||||
$codebase
|
||||
);
|
||||
}
|
||||
|
||||
$keys_to_unset[] = $key;
|
||||
|
||||
foreach ($class_template_type->getAtomicTypes() as $class_template_atomic_type) {
|
||||
$new_types[$class_template_atomic_type->getKey()] = $class_template_atomic_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,16 +71,15 @@ class AnnotationTest extends TestCase
|
||||
'<?php
|
||||
/** @psalm-suppress MissingConstructor */
|
||||
class Foo {
|
||||
/** @var \stdClass[]|\ArrayObject */
|
||||
public $bar;
|
||||
/** @var \stdClass[]|\ArrayObject */
|
||||
public $bar;
|
||||
|
||||
/**
|
||||
* @return \stdClass[]|\ArrayObject
|
||||
*/
|
||||
public function getBar(): \ArrayObject
|
||||
{
|
||||
return $this->bar;
|
||||
}
|
||||
/**
|
||||
* @return \stdClass[]|\ArrayObject
|
||||
*/
|
||||
public function getBar(): \ArrayObject {
|
||||
return $this->bar;
|
||||
}
|
||||
}'
|
||||
);
|
||||
|
||||
@ -565,12 +564,11 @@ class AnnotationTest extends TestCase
|
||||
],
|
||||
'builtInClassInAShape' => [
|
||||
'<?php
|
||||
/**
|
||||
* @return array{d:Exception}
|
||||
* @psalm-suppress InvalidReturnType
|
||||
*/
|
||||
function f() {}
|
||||
'
|
||||
/**
|
||||
* @return array{d:Exception}
|
||||
* @psalm-suppress InvalidReturnType
|
||||
*/
|
||||
function f() {}'
|
||||
],
|
||||
'slashAfter?' => [
|
||||
'<?php
|
||||
@ -786,10 +784,10 @@ class AnnotationTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-type _A=array{elt:int}
|
||||
* @param _A $p
|
||||
* @return _A
|
||||
*/
|
||||
* @psalm-type _A=array{elt:int}
|
||||
* @param _A $p
|
||||
* @return _A
|
||||
*/
|
||||
function f($p) {
|
||||
/** @var _A */
|
||||
$r = $p;
|
||||
@ -1010,7 +1008,7 @@ class AnnotationTest extends TestCase
|
||||
* } $foo
|
||||
*/
|
||||
function foo(array $foo) : int {
|
||||
return count($foo);
|
||||
return count($foo);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1075,7 +1073,7 @@ class AnnotationTest extends TestCase
|
||||
'possiblyUndefinedObjectProperty' => [
|
||||
'<?php
|
||||
function consume(string $value): void {
|
||||
echo $value;
|
||||
echo $value;
|
||||
}
|
||||
|
||||
/** @var object{value?: string} $data */
|
||||
@ -1090,9 +1088,8 @@ class AnnotationTest extends TestCase
|
||||
/**
|
||||
* @throws self
|
||||
*/
|
||||
public static function create(): void
|
||||
{
|
||||
throw new self();
|
||||
public static function create(): void {
|
||||
throw new self();
|
||||
}
|
||||
}'
|
||||
],
|
||||
@ -1122,6 +1119,38 @@ class AnnotationTest extends TestCase
|
||||
return false;
|
||||
}'
|
||||
],
|
||||
'conditionalReturnType' => [
|
||||
'<?php
|
||||
|
||||
class A {
|
||||
/** @var array<string, string> */
|
||||
private array $itemAttr = [];
|
||||
|
||||
/**
|
||||
* @template T as ?string
|
||||
* @param T $name
|
||||
* @return string|string[]
|
||||
* @psalm-return (T is string ? string : array<string, string>)
|
||||
*/
|
||||
public function getAttribute(?string $name, string $default = "")
|
||||
{
|
||||
if (null === $name) {
|
||||
return $this->itemAttr;
|
||||
}
|
||||
return isset($this->itemAttr[$name]) ? $this->itemAttr[$name] : $default;
|
||||
}
|
||||
}
|
||||
|
||||
$a = (new A)->getAttribute("colour", "red"); // typed as string
|
||||
$b = (new A)->getAttribute(null); // typed as array<string, string>
|
||||
/** @psalm-suppress MixedArgument */
|
||||
$c = (new A)->getAttribute($_GET["foo"]); // typed as string|array<string, string>',
|
||||
[
|
||||
'$a' => 'string',
|
||||
'$b' => 'array<string, string>',
|
||||
'$c' => 'array<string, string>|string'
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -1233,7 +1262,7 @@ class AnnotationTest extends TestCase
|
||||
* @return \?string
|
||||
*/
|
||||
function foo() {
|
||||
return rand(0, 1) ? "hello" : null;
|
||||
return rand(0, 1) ? "hello" : null;
|
||||
}',
|
||||
'error_message' => 'InvalidDocblock',
|
||||
],
|
||||
@ -1336,7 +1365,7 @@ class AnnotationTest extends TestCase
|
||||
* @psalm-suppress MismatchingDocblockReturnType
|
||||
*/
|
||||
function foo(): B {
|
||||
return new A;
|
||||
return new A;
|
||||
}',
|
||||
'error_message' => 'UndefinedClass',
|
||||
],
|
||||
|
@ -551,6 +551,47 @@ class TypeParseTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testConditionalTypeWithSpaces()
|
||||
{
|
||||
$this->assertSame(
|
||||
'(T is string ? string : int)',
|
||||
(string) Type::parseString('(T is string ? string : int)', null, ['T' => ['' => [Type::getArray()]]])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testConditionalTypeWithUnion()
|
||||
{
|
||||
$this->assertSame(
|
||||
'(T is string|true ? int|string : int)',
|
||||
(string) Type::parseString('(T is "hello"|true ? string|int : int)', null, ['T' => ['' => [Type::getArray()]]])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testConditionalTypeWithoutSpaces()
|
||||
{
|
||||
$this->assertSame(
|
||||
'(T is string ? string : int)',
|
||||
(string) Type::parseString('(T is string?string:int)', null, ['T' => ['' => [Type::getArray()]]])
|
||||
);
|
||||
}
|
||||
|
||||
public function testConditionalTypeWithGenerics() : void
|
||||
{
|
||||
$this->assertSame(
|
||||
'(T is string ? string : array<string, string>)',
|
||||
(string) Type::parseString('(T is string ? string : array<string, string>)', null, ['T' => ['' => [Type::getArray()]]])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user