1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Allow arbitrary bracketing of types

This commit is contained in:
Matthew Brown 2018-03-22 22:28:06 -04:00
parent c54850aa5b
commit a96ce89e9f
4 changed files with 67 additions and 41 deletions

View File

@ -45,10 +45,6 @@ abstract class Type
// remove all unacceptable characters
$type_string = preg_replace('/[^A-Za-z0-9\-_\\\\&|\? \<\>\{\}:,\]\[\(\)\$]/', '', trim($type_string));
if (strpos($type_string, '[') !== false) {
$type_string = self::convertSquareBrackets($type_string);
}
$type_string = preg_replace('/\?(?=[a-zA-Z])/', 'null|', $type_string);
$type_tokens = self::tokenize($type_string);
@ -190,8 +186,8 @@ abstract class Type
$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)
throw new TypeParseTreeException(
'Intersection types cannot contain unions'
);
}
@ -256,6 +252,10 @@ abstract class Type
return new ObjectLike($properties);
}
if ($parse_tree instanceof ParseTree\EncapsulationTree) {
return self::getTypeFromTree($parse_tree->children[0], false);
}
if (!$parse_tree instanceof ParseTree\Value) {
throw new \InvalidArgumentException('Unrecognised parse tree type');
}
@ -337,14 +337,10 @@ abstract class 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)) {
if (in_array($return_type_token, ['<', '>', '|', '?', ',', '{', '}', ':', '[', ']', '(', ')'], true)) {
continue;
}
@ -405,35 +401,6 @@ abstract class Type
return ($namespace ? $namespace . '\\' : '') . $class;
}
/**
* @param string $type
*
* @return string
*/
public static function convertSquareBrackets($type)
{
$class_chars = '[a-zA-Z0-9\<\>\\\\_]+';
return preg_replace_callback(
'/(' . $class_chars . '|' . '\((' . $class_chars . '(\|' . $class_chars . ')*' . ')\))((\[\])+)/',
/**
* @return string
*/
function (array $matches) {
$inner_type = str_replace(['(', ')'], '', (string)$matches[1]);
$dimensionality = strlen((string)$matches[4]) / 2;
for ($i = 0; $i < $dimensionality; ++$i) {
$inner_type = 'array<mixed,' . $inner_type . '>';
}
return $inner_type;
},
$type
);
}
/**
* @return Type\Union
*/

View File

@ -75,6 +75,40 @@ class ParseTree
++$i;
break;
case '(':
if ($current_leaf instanceof ParseTree\Value) {
throw new TypeParseTreeException('Unrecognised token (');
}
$new_parent = !$current_leaf instanceof ParseTree\Root ? $current_leaf : null;
$new_leaf = new ParseTree\EncapsulationTree(
$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;
case ')':
do {
if ($current_leaf->parent === null) {
throw new TypeParseTreeException('Cannot parse generic type');
}
$current_leaf = $current_leaf->parent;
} while (!$current_leaf instanceof ParseTree\EncapsulationTree);
break;
case '>':
do {
if ($current_leaf->parent === null) {
@ -243,7 +277,7 @@ class ParseTree
if ($current_leaf instanceof ParseTree\Root) {
$current_leaf = $parse_tree = $new_leaf;
continue;
break;
}
if ($new_leaf->parent) {
@ -251,6 +285,7 @@ class ParseTree
}
$current_leaf = $new_leaf;
break;
}
}

View File

@ -0,0 +1,6 @@
<?php
namespace Psalm\Type\ParseTree;
class EncapsulationTree extends \Psalm\Type\ParseTree
{
}

View File

@ -37,6 +37,14 @@ class TypeParseTest extends TestCase
$this->assertSame('int|string', (string) Type::parseString('int|string'));
}
/**
* @return void
*/
public function testBracketedIntOrString()
{
$this->assertSame('int|string', (string) Type::parseString('(int|string)'));
}
/**
* @return void
*/
@ -215,6 +223,16 @@ class TypeParseTest extends TestCase
Type::parseString('array(A)');
}
/**
* @expectedException \Psalm\Exception\TypeParseTreeException
*
* @return void
*/
public function testBracketedUnionAndIntersection()
{
Type::parseString('(A|B)&C');
}
/**
* @return void
*/