Skip to content

Commit

Permalink
Add prototype for conditional return type
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Mar 22, 2020
1 parent 2669434 commit 6058725
Show file tree
Hide file tree
Showing 9 changed files with 498 additions and 59 deletions.
23 changes: 23 additions & 0 deletions src/Psalm/Internal/Analyzer/TypeAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
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;
Expand Down Expand Up @@ -913,6 +914,28 @@ public static function isAtomicContainedBy(
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) {
Expand Down
114 changes: 85 additions & 29 deletions src/Psalm/Internal/Type/ParseTree.php
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,19 @@ public static function createFromTokens(array $type_tokens)
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');
}
Expand Down Expand Up @@ -372,22 +385,44 @@ public static function createFromTokens(array $type_tokens)

case '?':
if ($next_token === null || $next_token[0] !== ':') {
$new_parent = !$current_leaf instanceof ParseTree\Root ? $current_leaf : null;
while (($current_leaf instanceof ParseTree\Value
|| $current_leaf instanceof ParseTree\UnionTree)
&& $current_leaf->parent
) {
$current_leaf = $current_leaf->parent;
}

$new_leaf = new ParseTree\NullableTree(
$new_parent
);
if ($current_leaf instanceof ParseTree\TemplateIsTree && $current_leaf->parent) {
$current_parent = $current_leaf->parent;

if ($current_leaf instanceof ParseTree\Root) {
$current_leaf = $parse_tree = $new_leaf;
break;
}
$new_leaf = new ParseTree\ConditionalTree(
$current_leaf,
$current_leaf->parent
);

if ($new_leaf->parent) {
$new_leaf->parent->children[] = $new_leaf;
}
$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;
}

$current_leaf = $new_leaf;
if ($new_leaf->parent) {
$new_leaf->parent->children[] = $new_leaf;
}

$current_leaf = $new_leaf;
}
}

break;
Expand Down Expand Up @@ -423,9 +458,16 @@ public static function createFromTokens(array $type_tokens)
$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);
Expand Down Expand Up @@ -472,32 +514,46 @@ public static function createFromTokens(array $type_tokens)

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;
} elseif ($current_leaf instanceof ParseTree\Value) {
$current_leaf = new ParseTree\TemplateIsTree(
$current_leaf->value,
$current_parent
);

$current_parent->children[] = $current_leaf;
++$i;
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;
Expand Down
19 changes: 19 additions & 0 deletions src/Psalm/Internal/Type/ParseTree/ConditionalTree.php
Original file line number Diff line number Diff line change
@@ -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 changes: 19 additions & 0 deletions src/Psalm/Internal/Type/ParseTree/TemplateIsTree.php
Original file line number Diff line number Diff line change
@@ -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;
}
}
53 changes: 50 additions & 3 deletions src/Psalm/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,53 @@ function (ParseTree $child_tree) use ($template_type_map) {
);
}

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));
}
Expand Down Expand Up @@ -905,11 +952,11 @@ public static function tokenize($string_type, $ignore_space = true)
$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) {
Expand Down Expand Up @@ -1079,7 +1126,7 @@ public static function fixUpLocalType(
if (in_array(
$string_type_token[0],
[
'<', '>', '|', '?', ',', '{', '}', ':', '::', '[', ']', '(', ')', '&', '=', '...', 'as',
'<', '>', '|', '?', ',', '{', '}', ':', '::', '[', ']', '(', ')', '&', '=', '...', 'as', 'is',
],
true
)) {
Expand Down
Loading

0 comments on commit 6058725

Please sign in to comment.