mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
parent
871a91c850
commit
58115599a1
@ -68,6 +68,7 @@
|
||||
<PossiblyUnusedMethod>
|
||||
<errorLevel type="suppress">
|
||||
<directory name="tests" />
|
||||
<file name="src/Psalm/Type/Atomic/CallableTrait.php" />
|
||||
<file name="src/Psalm/Type/Atomic/GenericTrait.php" />
|
||||
<file name="src/Psalm/Plugin.php" />
|
||||
<referencedMethod name="Psalm\Codebase::getParentInterfaces" />
|
||||
|
@ -13,7 +13,7 @@ use Psalm\VarDocblockComment;
|
||||
|
||||
class CommentChecker
|
||||
{
|
||||
const TYPE_REGEX = '(\??\\\?[A-Za-z][\(\)A-Za-z0-9_&\<,\>\[\]\-\{\}:|?\\\\]*|\$[a-zA-Z_0-9_&\<,\>\|\[\]-\{\}:]+)';
|
||||
const TYPE_REGEX = '(\??\\\?[A-Za-z][\(\)A-Za-z0-9_&\<\.=,\>\[\]\-\{\}:|?\\\\]*|\$[a-zA-Z_0-9_]+)';
|
||||
|
||||
/**
|
||||
* @param string $comment
|
||||
@ -383,8 +383,13 @@ class CommentChecker
|
||||
|
||||
$type = '';
|
||||
|
||||
for ($i = 0; $i < strlen($return_block); ++$i) {
|
||||
$expects_callable_return = false;
|
||||
|
||||
$return_block = preg_replace('/[ \t]+/', ' ', $return_block);
|
||||
|
||||
for ($i = 0, $l = strlen($return_block); $i < $l; ++$i) {
|
||||
$char = $return_block[$i];
|
||||
$next_char = $i < $l - 1 ? $return_block[$i + 1] : null;
|
||||
|
||||
if ($char === '[' || $char === '{' || $char === '(' || $char === '<') {
|
||||
$brackets .= $char;
|
||||
@ -404,10 +409,21 @@ class CommentChecker
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($next_char === ':') {
|
||||
++$i;
|
||||
$expects_callable_return = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($expects_callable_return) {
|
||||
$expects_callable_return = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$remaining = trim(substr($return_block, $i + 1));
|
||||
|
||||
if ($remaining) {
|
||||
return array_merge([$type], preg_split('/[\s\t]+/', $remaining));
|
||||
return array_merge([$type], explode(' ', $remaining));
|
||||
}
|
||||
|
||||
return [$type];
|
||||
|
@ -373,7 +373,7 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
&& ($closure_atomic_type = $function_call_arg->value->inferredType->getTypes()['Closure'])
|
||||
&& $closure_atomic_type instanceof Type\Atomic\Fn
|
||||
) {
|
||||
$closure_return_type = $closure_atomic_type->return_type;
|
||||
$closure_return_type = $closure_atomic_type->return_type ?: Type::getMixed();
|
||||
|
||||
if ($closure_return_type->isVoid()) {
|
||||
IssueBuffer::accepts(
|
||||
@ -591,7 +591,7 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
&& ($closure_atomic_type = $function_call_arg->value->inferredType->getTypes()['Closure'])
|
||||
&& $closure_atomic_type instanceof Type\Atomic\Fn
|
||||
) {
|
||||
$closure_return_type = $closure_atomic_type->return_type;
|
||||
$closure_return_type = $closure_atomic_type->return_type ?: Type::getMixed();
|
||||
|
||||
if ($closure_return_type->isVoid()) {
|
||||
IssueBuffer::accepts(
|
||||
|
@ -146,13 +146,13 @@ class FunctionCallChecker extends \Psalm\Checker\Statements\Expression\CallCheck
|
||||
if ($var_type_part instanceof Type\Atomic\Fn) {
|
||||
$function_params = $var_type_part->params;
|
||||
|
||||
if (isset($stmt->inferredType)) {
|
||||
if (isset($stmt->inferredType) && $var_type_part->return_type) {
|
||||
$stmt->inferredType = Type::combineUnionTypes(
|
||||
$stmt->inferredType,
|
||||
$var_type_part->return_type
|
||||
);
|
||||
} else {
|
||||
$stmt->inferredType = $var_type_part->return_type;
|
||||
$stmt->inferredType = $var_type_part->return_type ?: Type::getMixed();
|
||||
}
|
||||
|
||||
$function_exists = true;
|
||||
|
@ -1008,6 +1008,10 @@ class CallChecker
|
||||
}
|
||||
|
||||
foreach ($closure_types as $closure_type) {
|
||||
if ($closure_type->params === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::checkArrayFunctionClosureTypeArgs(
|
||||
$statements_checker,
|
||||
$method_id,
|
||||
@ -1043,6 +1047,10 @@ class CallChecker
|
||||
|
||||
$closure_params = $closure_type->params;
|
||||
|
||||
if ($closure_params === null) {
|
||||
throw new \UnexpectedValueException('Closure params should not be null here');
|
||||
}
|
||||
|
||||
$required_param_count = 0;
|
||||
|
||||
foreach ($closure_params as $i => $param) {
|
||||
|
@ -83,4 +83,9 @@ class FunctionLikeParameter
|
||||
$this->type_location = $type_location;
|
||||
$this->signature_type_location = $type_location;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return ($this->is_variadic ? '...' : '') . $this->type . ($this->is_optional ? '=' : '');
|
||||
}
|
||||
}
|
||||
|
@ -25,8 +25,9 @@ class ClassLikeStorageCacheProvider
|
||||
$storage_dir = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Storage' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$dependent_files = [
|
||||
$storage_dir . 'ClassLikeStorage.php',
|
||||
$storage_dir . 'FileStorage.php',
|
||||
$storage_dir . 'FunctionLikeStorage.php',
|
||||
$storage_dir . 'ClassLikeStorage.php',
|
||||
$storage_dir . 'MethodStorage.php',
|
||||
];
|
||||
|
||||
|
@ -29,6 +29,7 @@ class FileStorageCacheProvider
|
||||
$storage_dir . 'FunctionLikeStorage.php',
|
||||
$storage_dir . 'ClassLikeStorage.php',
|
||||
$storage_dir . 'MethodStorage.php',
|
||||
dirname(__DIR__) . DIRECTORY_SEPARATOR . 'FunctionLikeParameter.php',
|
||||
];
|
||||
|
||||
foreach ($dependent_files as $dependent_file_path) {
|
||||
|
@ -2,10 +2,12 @@
|
||||
namespace Psalm;
|
||||
|
||||
use Psalm\Exception\TypeParseTreeException;
|
||||
use Psalm\FunctionLikeParameter;
|
||||
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;
|
||||
@ -43,7 +45,7 @@ abstract class Type
|
||||
public static function parseString($type_string, $php_compatible = false)
|
||||
{
|
||||
// remove all unacceptable characters
|
||||
$type_string = preg_replace('/[^A-Za-z0-9\-_\\\\&|\? \<\>\{\}:,\]\[\(\)\$]/', '', trim($type_string));
|
||||
$type_string = preg_replace('/[^A-Za-z0-9\-_\\\\&|\? \<\>\{\}=:\.,\]\[\(\)\$]/', '', trim($type_string));
|
||||
|
||||
$type_string = preg_replace('/\?(?=[a-zA-Z])/', 'null|', $type_string);
|
||||
|
||||
@ -254,12 +256,66 @@ abstract class Type
|
||||
return new ObjectLike($properties);
|
||||
}
|
||||
|
||||
if ($parse_tree instanceof ParseTree\CallableWithReturnTypeTree) {
|
||||
$callable_type = self::getTypeFromTree($parse_tree->children[0], false);
|
||||
|
||||
if (!$callable_type instanceof TCallable) {
|
||||
throw new \InvalidArgumentException('Parsing callable tree node should return TCallable');
|
||||
}
|
||||
|
||||
$return_type = self::getTypeFromTree($parse_tree->children[1], false);
|
||||
|
||||
$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) {
|
||||
$is_variadic = false;
|
||||
$is_optional = false;
|
||||
|
||||
if ($child_tree instanceof ParseTree\CallableParamTree) {
|
||||
$tree_type = self::getTypeFromTree($child_tree->children[0], false);
|
||||
$is_variadic = $child_tree->variadic;
|
||||
$is_optional = $child_tree->has_default;
|
||||
} else {
|
||||
$tree_type = self::getTypeFromTree($child_tree, false);
|
||||
}
|
||||
|
||||
$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($parse_tree->value, ['closure', '\closure'], false)) {
|
||||
return new Type\Atomic\Fn('Closure', $params);
|
||||
}
|
||||
|
||||
return new TCallable($parse_tree->value, $params);
|
||||
}
|
||||
|
||||
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');
|
||||
throw new \InvalidArgumentException('Unrecognised parse tree type ' . get_class($parse_tree));
|
||||
}
|
||||
|
||||
$atomic_type = self::fixScalarTerms($parse_tree->value, $php_compatible);
|
||||
@ -289,25 +345,29 @@ abstract class Type
|
||||
// index of last type token
|
||||
$rtc = 0;
|
||||
|
||||
foreach (str_split($return_type) as $char) {
|
||||
$chars = str_split($return_type);
|
||||
for ($i = 0, $c = count($chars); $i < $c; ++$i) {
|
||||
$char = $chars[$i];
|
||||
|
||||
if ($was_char) {
|
||||
$return_type_tokens[++$rtc] = '';
|
||||
}
|
||||
|
||||
if ($char === '<' ||
|
||||
$char === '>' ||
|
||||
$char === '|' ||
|
||||
$char === '?' ||
|
||||
$char === ',' ||
|
||||
$char === '{' ||
|
||||
$char === '}' ||
|
||||
$char === '[' ||
|
||||
$char === ']' ||
|
||||
$char === '(' ||
|
||||
$char === ')' ||
|
||||
$char === ' ' ||
|
||||
$char === '&' ||
|
||||
$char === ':'
|
||||
if ($char === '<'
|
||||
|| $char === '>'
|
||||
|| $char === '|'
|
||||
|| $char === '?'
|
||||
|| $char === ','
|
||||
|| $char === '{'
|
||||
|| $char === '}'
|
||||
|| $char === '['
|
||||
|| $char === ']'
|
||||
|| $char === '('
|
||||
|| $char === ')'
|
||||
|| $char === ' '
|
||||
|| $char === '&'
|
||||
|| $char === ':'
|
||||
|| $char === '='
|
||||
) {
|
||||
if ($return_type_tokens[$rtc] === '') {
|
||||
$return_type_tokens[$rtc] = $char;
|
||||
@ -316,6 +376,20 @@ abstract class Type
|
||||
}
|
||||
|
||||
$was_char = true;
|
||||
} elseif ($char === '.') {
|
||||
if ($i + 1 > $c || $chars[$i + 1] !== '.' || $chars[$i + 2] !== '.') {
|
||||
throw new TypeParseTreeException('Unexpected token ' . $char);
|
||||
}
|
||||
|
||||
if ($return_type_tokens[$rtc] === '') {
|
||||
$return_type_tokens[$rtc] = '...';
|
||||
} else {
|
||||
$return_type_tokens[++$rtc] = '...';
|
||||
}
|
||||
|
||||
$was_char = true;
|
||||
|
||||
$i += 2;
|
||||
} else {
|
||||
$return_type_tokens[$rtc] .= $char;
|
||||
$was_char = false;
|
||||
|
101
src/Psalm/Type/Atomic/CallableTrait.php
Normal file
101
src/Psalm/Type/Atomic/CallableTrait.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
use Psalm\FunctionLikeParameter;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
trait CallableTrait
|
||||
{
|
||||
/**
|
||||
* @var array<int, FunctionLikeParameter>|null
|
||||
*/
|
||||
public $params = [];
|
||||
|
||||
/**
|
||||
* @var Union|null
|
||||
*/
|
||||
public $return_type;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of a generic type
|
||||
*
|
||||
* @param string $value
|
||||
* @param array<int, FunctionLikeParameter> $params
|
||||
* @param Union $return_type
|
||||
*/
|
||||
public function __construct($value = 'callable', array $params = null, Union $return_type = null)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->params = $params;
|
||||
$this->return_type = $return_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $namespace
|
||||
* @param array<string> $aliased_classes
|
||||
* @param string|null $this_class
|
||||
* @param bool $use_phpdoc_format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toNamespacedString($namespace, array $aliased_classes, $this_class, $use_phpdoc_format)
|
||||
{
|
||||
if ($use_phpdoc_format) {
|
||||
if ($this instanceof TNamedObject) {
|
||||
return parent::toNamespacedString($namespace, $aliased_classes, $this_class, true);
|
||||
}
|
||||
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
$param_string = '';
|
||||
$return_type_string = '';
|
||||
|
||||
if ($this->params !== null) {
|
||||
$param_string = '(' . implode(
|
||||
', ',
|
||||
array_map(
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function (FunctionLikeParameter $param) use ($namespace, $aliased_classes, $this_class) {
|
||||
if (!$param->type) {
|
||||
throw new \UnexpectedValueException('Param type must not be null');
|
||||
}
|
||||
|
||||
$type_string = $param->type->toNamespacedString(
|
||||
$namespace,
|
||||
$aliased_classes,
|
||||
$this_class,
|
||||
false
|
||||
);
|
||||
|
||||
return ($param->is_variadic ? '...' : '') . $type_string . ($param->is_optional ? '=' : '');
|
||||
},
|
||||
$this->params
|
||||
)
|
||||
) . ')';
|
||||
}
|
||||
|
||||
if ($this->return_type !== null) {
|
||||
$return_type_string = ' : ' . $this->return_type->toNamespacedString(
|
||||
$namespace,
|
||||
$aliased_classes,
|
||||
$this_class,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if ($this instanceof TNamedObject) {
|
||||
return parent::toNamespacedString($namespace, $aliased_classes, $this_class, true)
|
||||
. $param_string . $return_type_string;
|
||||
}
|
||||
|
||||
return 'callable' . $param_string . $return_type_string;
|
||||
}
|
||||
}
|
@ -9,29 +9,7 @@ use Psalm\Type\Union;
|
||||
*/
|
||||
class Fn extends TNamedObject
|
||||
{
|
||||
/**
|
||||
* @var array<int, FunctionLikeParameter>
|
||||
*/
|
||||
public $params = [];
|
||||
|
||||
/**
|
||||
* @var Union
|
||||
*/
|
||||
public $return_type;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of a generic type
|
||||
*
|
||||
* @param string $value
|
||||
* @param array<int, FunctionLikeParameter> $params
|
||||
* @param Union $return_type
|
||||
*/
|
||||
public function __construct($value, array $params, Union $return_type)
|
||||
{
|
||||
$this->value = 'Closure';
|
||||
$this->params = $params;
|
||||
$this->return_type = $return_type;
|
||||
}
|
||||
use CallableTrait;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
|
@ -3,9 +3,22 @@ namespace Psalm\Type\Atomic;
|
||||
|
||||
class TCallable extends \Psalm\Type\Atomic
|
||||
{
|
||||
use CallableTrait;
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return 'callable';
|
||||
$param_string = '';
|
||||
$return_type_string = '';
|
||||
|
||||
if ($this->params !== null) {
|
||||
$param_string = '(' . implode(', ', $this->params) . ')';
|
||||
}
|
||||
|
||||
if ($this->return_type !== null) {
|
||||
$return_type_string = ' : ' . $this->return_type;
|
||||
}
|
||||
|
||||
return 'callable' . $param_string . $return_type_string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -23,7 +36,7 @@ class TCallable extends \Psalm\Type\Atomic
|
||||
* @param int $php_major_version
|
||||
* @param int $php_minor_version
|
||||
*
|
||||
* @return null|string
|
||||
* @return string
|
||||
*/
|
||||
public function toPhpString(
|
||||
$namespace,
|
||||
@ -32,7 +45,7 @@ class TCallable extends \Psalm\Type\Atomic
|
||||
$php_major_version,
|
||||
$php_minor_version
|
||||
) {
|
||||
return null;
|
||||
return 'callable';
|
||||
}
|
||||
|
||||
public function canBeFullyExpressedInPhp()
|
||||
|
@ -105,7 +105,8 @@ class ParseTree
|
||||
}
|
||||
|
||||
$current_leaf = $current_leaf->parent;
|
||||
} while (!$current_leaf instanceof ParseTree\EncapsulationTree);
|
||||
} while (!$current_leaf instanceof ParseTree\EncapsulationTree
|
||||
&& !$current_leaf instanceof ParseTree\CallableTree);
|
||||
|
||||
break;
|
||||
|
||||
@ -142,6 +143,7 @@ class ParseTree
|
||||
|
||||
if ($context_node instanceof ParseTree\GenericTree
|
||||
|| $context_node instanceof ParseTree\ObjectLikeTree
|
||||
|| $context_node instanceof ParseTree\CallableTree
|
||||
) {
|
||||
$context_node = $context_node->parent;
|
||||
}
|
||||
@ -149,6 +151,7 @@ class ParseTree
|
||||
while ($context_node
|
||||
&& !$context_node instanceof ParseTree\GenericTree
|
||||
&& !$context_node instanceof ParseTree\ObjectLikeTree
|
||||
&& !$context_node instanceof ParseTree\CallableTree
|
||||
) {
|
||||
$context_node = $context_node->parent;
|
||||
}
|
||||
@ -161,11 +164,70 @@ class ParseTree
|
||||
|
||||
break;
|
||||
|
||||
case '...':
|
||||
if (!$current_leaf instanceof ParseTree\CallableTree) {
|
||||
throw new TypeParseTreeException('Unexpected token ' . $type_token);
|
||||
}
|
||||
|
||||
$new_leaf = new ParseTree\CallableParamTree($current_leaf);
|
||||
$new_leaf->variadic = true;
|
||||
$current_leaf->children[] = $new_leaf;
|
||||
$current_leaf = $new_leaf;
|
||||
break;
|
||||
|
||||
case '=':
|
||||
$current_parent = $current_leaf->parent;
|
||||
|
||||
while ($current_parent
|
||||
&& !$current_parent instanceof ParseTree\CallableTree
|
||||
&& !$current_parent instanceof ParseTree\CallableParamTree
|
||||
) {
|
||||
$current_leaf = $current_parent;
|
||||
$current_parent = $current_parent->parent;
|
||||
}
|
||||
|
||||
if (!$current_parent || !$current_leaf) {
|
||||
throw new TypeParseTreeException('Unexpected token ' . $type_token);
|
||||
}
|
||||
|
||||
if ($current_parent instanceof ParseTree\CallableParamTree) {
|
||||
throw new TypeParseTreeException('Cannot have variadic param with a default');
|
||||
}
|
||||
|
||||
$new_leaf = new ParseTree\CallableParamTree($current_parent);
|
||||
$new_leaf->has_default = true;
|
||||
$new_leaf->children = [$current_leaf];
|
||||
|
||||
$current_leaf->parent = $new_leaf;
|
||||
|
||||
array_pop($current_parent->children);
|
||||
$current_parent->children[] = $new_leaf;
|
||||
|
||||
$current_leaf = $new_leaf;
|
||||
|
||||
break;
|
||||
|
||||
case ':':
|
||||
$current_parent = $current_leaf->parent;
|
||||
|
||||
if ($current_leaf instanceof ParseTree\CallableTree) {
|
||||
$new_parent_leaf = new ParseTree\CallableWithReturnTypeTree($current_parent);
|
||||
$current_leaf->parent = $new_parent_leaf;
|
||||
$new_parent_leaf->children = [$current_leaf];
|
||||
|
||||
if ($current_parent) {
|
||||
array_pop($current_parent->children);
|
||||
$current_parent->children[] = $new_parent_leaf;
|
||||
} else {
|
||||
$parse_tree = $new_parent_leaf;
|
||||
}
|
||||
|
||||
$current_leaf = $new_parent_leaf;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($current_parent && $current_parent instanceof ParseTree\ObjectLikePropertyTree) {
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$current_parent) {
|
||||
@ -261,7 +323,18 @@ class ParseTree
|
||||
break;
|
||||
|
||||
case '(':
|
||||
throw new TypeParseTreeException('Cannot process bracket yet');
|
||||
if (!in_array($type_token, ['closure', 'callable', '\closure'])) {
|
||||
throw new TypeParseTreeException(
|
||||
'Bracket must be preceded by “Closure” or “callable”'
|
||||
);
|
||||
}
|
||||
|
||||
$new_leaf = new ParseTree\CallableTree(
|
||||
$type_token,
|
||||
$new_parent
|
||||
);
|
||||
++$i;
|
||||
break;
|
||||
|
||||
default:
|
||||
if ($type_token === '$this') {
|
||||
|
15
src/Psalm/Type/ParseTree/CallableParamTree.php
Normal file
15
src/Psalm/Type/ParseTree/CallableParamTree.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace Psalm\Type\ParseTree;
|
||||
|
||||
class CallableParamTree extends \Psalm\Type\ParseTree
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $variadic = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $has_default = false;
|
||||
}
|
20
src/Psalm/Type/ParseTree/CallableTree.php
Normal file
20
src/Psalm/Type/ParseTree/CallableTree.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
namespace Psalm\Type\ParseTree;
|
||||
|
||||
class CallableTree extends \Psalm\Type\ParseTree
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @param \Psalm\Type\ParseTree|null $parent
|
||||
*/
|
||||
public function __construct($value, \Psalm\Type\ParseTree $parent = null)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->parent = $parent;
|
||||
}
|
||||
}
|
6
src/Psalm/Type/ParseTree/CallableWithReturnTypeTree.php
Normal file
6
src/Psalm/Type/ParseTree/CallableWithReturnTypeTree.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Psalm\Type\ParseTree;
|
||||
|
||||
class CallableWithReturnTypeTree extends \Psalm\Type\ParseTree
|
||||
{
|
||||
}
|
@ -585,6 +585,10 @@ class FunctionCallTest extends TestCase
|
||||
'<?php
|
||||
$a = function() use ($argv) : void {};',
|
||||
],
|
||||
'SKIPPED-implodeMultiDimensionalArray' => [
|
||||
'<?php
|
||||
$urls = array_map("implode", [["a", "b"]]);',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -300,4 +300,89 @@ class TypeParseTest extends TestCase
|
||||
(string)Type::parseString('array{a:int, b?:int}')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCallable()
|
||||
{
|
||||
$this->assertSame(
|
||||
'callable(int, string) : void',
|
||||
(string)Type::parseString('callable(int, string) : void')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCallableWithUnionLastType()
|
||||
{
|
||||
$this->assertSame(
|
||||
'callable(int, int|string) : void',
|
||||
(string)Type::parseString('callable(int, int|string) : void')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCallableWithVariadic()
|
||||
{
|
||||
$this->assertSame(
|
||||
'callable(int, ...string) : void',
|
||||
(string)Type::parseString('callable(int, ...string) : void')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Psalm\Exception\TypeParseTreeException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCallableWithBadVariadic()
|
||||
{
|
||||
Type::parseString('callable(int, ..string) : void');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Psalm\Exception\TypeParseTreeException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCallableWithVariadicAndDefault()
|
||||
{
|
||||
Type::parseString('callable(int, ...string=) : void');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Psalm\Exception\TypeParseTreeException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testBadVariadic()
|
||||
{
|
||||
Type::parseString('...string');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCallableWithDefault()
|
||||
{
|
||||
$this->assertSame(
|
||||
'callable(int, string=) : void',
|
||||
(string)Type::parseString('callable(int, string=) : void')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCallableWithoutReturn()
|
||||
{
|
||||
$this->assertSame(
|
||||
'callable(int, string)',
|
||||
(string)Type::parseString('callable(int, string)')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user