mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Fix more errors caught by Psalm
This commit is contained in:
parent
ad228e4d7e
commit
604c875d0c
@ -13,7 +13,7 @@ use Psalm\Checker\ProjectChecker;
|
||||
use Psalm\IssueBuffer;
|
||||
|
||||
// show all errors
|
||||
error_reporting(E_ALL);
|
||||
error_reporting(-1);
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
ini_set('memory_limit', '2048M');
|
||||
|
@ -766,6 +766,10 @@ abstract class ClassLikeChecker implements StatementsSource
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$class_name || strpos($class_name, '::') !== false) {
|
||||
throw new \InvalidArgumentException('Invalid class name ' . $class_name);
|
||||
}
|
||||
|
||||
try {
|
||||
$old_level = error_reporting();
|
||||
error_reporting(0);
|
||||
|
@ -139,7 +139,7 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
/**
|
||||
* @param PhpParser\Node\Stmt\Function_ $function
|
||||
* @param string $file_name
|
||||
* @return void
|
||||
* @return null|false
|
||||
*/
|
||||
protected function registerFunction(PhpParser\Node\Stmt\Function_ $function, $file_name)
|
||||
{
|
||||
@ -284,7 +284,9 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
$arg_name,
|
||||
$by_reference,
|
||||
$arg_type ? Type::parseString($arg_type) : Type::getMixed(),
|
||||
$optional
|
||||
$optional,
|
||||
false,
|
||||
$arg_name === '...'
|
||||
);
|
||||
}
|
||||
|
||||
@ -359,16 +361,16 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
$closure_return_types = \Psalm\EffectsAnalyser::getReturnTypes($function_call_arg->value->stmts, $closure_yield_types, true);
|
||||
|
||||
if (!$closure_return_types) {
|
||||
if (IssueBuffer::accepts(
|
||||
IssueBuffer::accepts(
|
||||
new InvalidReturnType(
|
||||
'No return type could be found in the closure passed to ' . $call_map_key,
|
||||
$file_name,
|
||||
$line_number
|
||||
),
|
||||
$suppressed_issues
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
return Type::getArray();
|
||||
}
|
||||
else {
|
||||
if ($call_map_key === 'array_map') {
|
||||
|
@ -812,12 +812,13 @@ abstract class FunctionLikeChecker implements StatementsSource
|
||||
*/
|
||||
public static function getParamsById($method_id, array $args, $file_name)
|
||||
{
|
||||
$absolute_class = strpos($method_id, '::') ? explode($method_id, '::')[0] : null;
|
||||
$absolute_class = strpos($method_id, '::') !== false ? explode('::', $method_id)[0] : null;
|
||||
|
||||
if ($absolute_class && ClassLikeChecker::isUserDefined($absolute_class)) {
|
||||
return MethodChecker::getMethodParams($method_id);
|
||||
}
|
||||
elseif (!$absolute_class && FunctionChecker::inCallMap($method_id)) {
|
||||
/** @var array<array<FunctionLikeParameter>> */
|
||||
$function_param_options = FunctionChecker::getParamsFromCallMap($method_id);
|
||||
}
|
||||
elseif ($absolute_class) {
|
||||
|
@ -85,6 +85,11 @@ class InterfaceChecker extends ClassLikeChecker
|
||||
self::$existing_interfaces_ci = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $interface_name
|
||||
* @param string $possible_parent
|
||||
* @return boolean
|
||||
*/
|
||||
public static function interfaceExtends($interface_name, $possible_parent)
|
||||
{
|
||||
return in_array($possible_parent, self::getParentInterfaces($interface_name));
|
||||
|
@ -65,7 +65,12 @@ class MethodChecker extends FunctionLikeChecker
|
||||
const VISIBILITY_PROTECTED = 2;
|
||||
const VISIBILITY_PRIVATE = 3;
|
||||
|
||||
public function __construct(PhpParser\Node\FunctionLike $function, StatementsSource $source, array $this_vars = [])
|
||||
/**
|
||||
* @param PhpParser\Node\FunctionLike $function
|
||||
* @param StatementsSource $source
|
||||
* @param array $this_vars
|
||||
*/
|
||||
public function __construct($function, StatementsSource $source, array $this_vars = [])
|
||||
{
|
||||
if (!$function instanceof PhpParser\Node\Stmt\ClassMethod) {
|
||||
throw new \InvalidArgumentException('Must be called with a ClassMethod');
|
||||
|
@ -1275,7 +1275,10 @@ class ExpressionChecker
|
||||
$first_arg_type = $stmt->args[0]->value->inferredType;
|
||||
|
||||
if ($first_arg_type->hasGeneric()) {
|
||||
/** @var Type\Union|null */
|
||||
$key_type = null;
|
||||
|
||||
/** @var Type\Union|null */
|
||||
$value_type = null;
|
||||
|
||||
foreach ($first_arg_type->types as $type) {
|
||||
@ -2016,7 +2019,7 @@ class ExpressionChecker
|
||||
(
|
||||
$this_class === $statements_checker->getAbsoluteClass() ||
|
||||
ClassChecker::classExtends($this_class, $statements_checker->getAbsoluteClass()) ||
|
||||
trait_exists($statements_checker->getAbsoluteClass())
|
||||
TraitChecker::traitExists($statements_checker->getAbsoluteClass())
|
||||
)) {
|
||||
|
||||
$method_id = $statements_checker->getAbsoluteClass() . '::' . strtolower($stmt->name);
|
||||
@ -2034,6 +2037,7 @@ class ExpressionChecker
|
||||
$has_mock = false;
|
||||
|
||||
if ($class_type && is_string($stmt->name)) {
|
||||
/** @var Type\Union|null */
|
||||
$return_type = null;
|
||||
|
||||
foreach ($class_type->types as $type) {
|
||||
@ -2381,7 +2385,8 @@ class ExpressionChecker
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($stmt->class->parts[0] !== 'parent'
|
||||
if ($stmt->class instanceof PhpParser\Node\Name
|
||||
&& $stmt->class->parts[0] !== 'parent'
|
||||
&& ($statements_checker->isStatic() || !ClassChecker::classExtends($context->self, $absolute_class))
|
||||
) {
|
||||
if (MethodChecker::checkMethodStatic($method_id, $statements_checker->getCheckedFileName(), $stmt->getLine(), $statements_checker->getSuppressedIssues()) === false) {
|
||||
@ -2396,7 +2401,14 @@ class ExpressionChecker
|
||||
$return_types = MethodChecker::getMethodReturnTypes($method_id);
|
||||
|
||||
if ($return_types) {
|
||||
$return_types = self::fleshOutTypes($return_types, $stmt->args, $stmt->class->parts === ['parent'] ? $statements_checker->getAbsoluteClass() : $absolute_class, $method_id);
|
||||
$return_types = self::fleshOutTypes(
|
||||
$return_types,
|
||||
$stmt->args,
|
||||
$stmt->class instanceof PhpParser\Node\Name && $stmt->class->parts === ['parent']
|
||||
? $statements_checker->getAbsoluteClass()
|
||||
: $absolute_class,
|
||||
$method_id
|
||||
);
|
||||
|
||||
if (isset($stmt->inferredType)) {
|
||||
$stmt->inferredType = Type::combineUnionTypes($stmt->inferredType, $return_types);
|
||||
@ -2416,11 +2428,11 @@ class ExpressionChecker
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpParser\Node\Arg[] $args
|
||||
* @param string|null $method_id
|
||||
* @param Context $context
|
||||
* @param int $line_number
|
||||
* @param boolean $is_mock
|
||||
* @param array<int, PhpParser\Node\Arg> $args
|
||||
* @param string|null $method_id
|
||||
* @param Context $context
|
||||
* @param int $line_number
|
||||
* @param boolean $is_mock
|
||||
* @return false|null
|
||||
*/
|
||||
protected static function checkFunctionArguments(StatementsChecker $statements_checker, array $args, $method_id, Context $context, $line_number, $is_mock = false)
|
||||
@ -2521,7 +2533,6 @@ class ExpressionChecker
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$has_packed_var = false;
|
||||
|
||||
foreach ($args as $arg) {
|
||||
@ -2559,24 +2570,38 @@ class ExpressionChecker
|
||||
}
|
||||
|
||||
if ($method_id === 'array_map' || $method_id === 'array_filter') {
|
||||
$array_index = $method_id === 'array_map' ? 1 : 0;
|
||||
$closure_index = $method_id === 'array_map' ? 0 : 1;
|
||||
|
||||
$array_arg = isset($args[$array_index]->value) ? $args[$array_index]->value : null;
|
||||
$array_arg_types = [];
|
||||
|
||||
$array_arg_type = $array_arg
|
||||
&& isset($array_arg->inferredType)
|
||||
&& isset($array_arg->inferredType->types['array'])
|
||||
&& $array_arg->inferredType->types['array'] instanceof Type\Generic
|
||||
? $array_arg->inferredType->types['array']
|
||||
: null;
|
||||
foreach ($args as $i => $arg) {
|
||||
if ($i === 0 && $method_id === 'array_map') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($i === 1 && $method_id === 'array_filter') {
|
||||
break;
|
||||
}
|
||||
|
||||
$array_arg = isset($args[$i]->value) ? $args[$i]->value : null;
|
||||
|
||||
$array_arg_types[] = $array_arg
|
||||
&& isset($array_arg->inferredType)
|
||||
&& isset($array_arg->inferredType->types['array'])
|
||||
&& $array_arg->inferredType->types['array'] instanceof Type\Generic
|
||||
? $array_arg->inferredType->types['array']
|
||||
: null;
|
||||
}
|
||||
|
||||
/** @var PhpParser\Node\Expr\Closure|null */
|
||||
$closure_arg = isset($args[$closure_index]) && $args[$closure_index]->value instanceof PhpParser\Node\Expr\Closure
|
||||
? $args[$closure_index]->value
|
||||
: null;
|
||||
|
||||
if ($array_arg_type && !$array_arg_type->type_params[1]->isMixed() && $closure_arg) {
|
||||
if (count($closure_arg->params) > 1) {
|
||||
if ($closure_arg) {
|
||||
$expected_closure_param_count = $method_id === 'array_filter' ? 1 : count($array_arg_types);
|
||||
|
||||
if (count($closure_arg->params) > $expected_closure_param_count) {
|
||||
if (IssueBuffer::accepts(
|
||||
new TooManyArguments(
|
||||
'Too many arguments in closure for ' . ($cased_method_id ?: $method_id),
|
||||
@ -2588,8 +2613,7 @@ class ExpressionChecker
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($closure_arg->params) === 0) {
|
||||
elseif (count($closure_arg->params) < $expected_closure_param_count) {
|
||||
if (IssueBuffer::accepts(
|
||||
new TooFewArguments(
|
||||
'You must supply a param in the closure for ' . ($cased_method_id ?: $method_id),
|
||||
@ -2602,38 +2626,25 @@ class ExpressionChecker
|
||||
}
|
||||
}
|
||||
|
||||
$closure_param = $closure_arg->params[0];
|
||||
|
||||
$translated_param = FunctionLikeChecker::getTranslatedParam(
|
||||
$closure_param,
|
||||
$statements_checker->getAbsoluteClass(),
|
||||
$statements_checker->getNamespace(),
|
||||
$statements_checker->getAliasedClasses()
|
||||
);
|
||||
|
||||
$param_type = $translated_param->type;
|
||||
$input_type = $array_arg_type->type_params[1];
|
||||
foreach ($closure_arg->params as $i => $closure_param) {
|
||||
$translated_param = FunctionLikeChecker::getTranslatedParam(
|
||||
$closure_param,
|
||||
$statements_checker->getAbsoluteClass(),
|
||||
$statements_checker->getNamespace(),
|
||||
$statements_checker->getAliasedClasses()
|
||||
);
|
||||
|
||||
$type_match_found = FunctionLikeChecker::doesParamMatch($input_type, $param_type, $scalar_type_match_found, $coerced_type);
|
||||
$param_type = $translated_param->type;
|
||||
$input_type = $array_arg_types[$i]->type_params[1];
|
||||
|
||||
if ($coerced_type) {
|
||||
if (IssueBuffer::accepts(
|
||||
new TypeCoercion(
|
||||
'First parameter of closure passed to function ' . $cased_method_id . ' expects ' . $param_type . ', parent type ' . $input_type . ' provided',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$closure_param->getLine()
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$type_match_found = FunctionLikeChecker::doesParamMatch($input_type, $param_type, $scalar_type_match_found, $coerced_type);
|
||||
|
||||
if (!$type_match_found) {
|
||||
if ($scalar_type_match_found) {
|
||||
if ($coerced_type) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidScalarArgument(
|
||||
'First parameter of closure passed to function ' . $cased_method_id . ' expects ' . $param_type . ', ' . $input_type . ' provided',
|
||||
new TypeCoercion(
|
||||
'First parameter of closure passed to function ' . $cased_method_id . ' expects ' . $param_type . ', parent type ' . $input_type . ' provided',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$closure_param->getLine()
|
||||
),
|
||||
@ -2642,15 +2653,30 @@ class ExpressionChecker
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (IssueBuffer::accepts(
|
||||
new InvalidArgument(
|
||||
'First parameter of closure passed to function ' . $cased_method_id . ' expects ' . $param_type . ', ' . $input_type . ' provided',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$closure_param->getLine()
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
|
||||
if (!$type_match_found) {
|
||||
if ($scalar_type_match_found) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidScalarArgument(
|
||||
'First parameter of closure passed to function ' . $cased_method_id . ' expects ' . $param_type . ', ' . $input_type . ' provided',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$closure_param->getLine()
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (IssueBuffer::accepts(
|
||||
new InvalidArgument(
|
||||
'First parameter of closure passed to function ' . $cased_method_id . ' expects ' . $param_type . ', ' . $input_type . ' provided',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$closure_param->getLine()
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -386,10 +386,8 @@ class StatementsChecker
|
||||
elseif ($stmt instanceof PhpParser\Node\Expr\UnaryMinus || $stmt instanceof PhpParser\Node\Expr\UnaryPlus) {
|
||||
return self::getSimpleType($stmt->expr);
|
||||
}
|
||||
else {
|
||||
var_dump('Unrecognised default property type');
|
||||
var_dump($stmt);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
@ -49,4 +49,13 @@ class TraitChecker extends ClassLikeChecker
|
||||
|
||||
return $method_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $trait_name
|
||||
* @return boolean
|
||||
*/
|
||||
public static function traitExists($trait_name)
|
||||
{
|
||||
return trait_exists($trait_name);
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ class TypeChecker
|
||||
return self::combineTypeAssertions($left_assertions, $right_assertions);
|
||||
}
|
||||
|
||||
return self::getTypeAssertions($conditional, $this_class_name, $namespace, $aliased_classes);
|
||||
return self::getTypeAssertions($conditional, $this_class_name, $namespace, $aliased_classes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -124,10 +124,16 @@ class TypeChecker
|
||||
* @param string $this_class_name
|
||||
* @param string $namespace
|
||||
* @param array<string> $aliased_classes
|
||||
* @param bool $allow_non_negatable Allow type assertions that should not be negated
|
||||
* @return array<string,string>
|
||||
*/
|
||||
public static function getTypeAssertions(PhpParser\Node\Expr $conditional, $this_class_name, $namespace, array $aliased_classes)
|
||||
{
|
||||
public static function getTypeAssertions(
|
||||
PhpParser\Node\Expr $conditional,
|
||||
$this_class_name,
|
||||
$namespace,
|
||||
array $aliased_classes,
|
||||
$allow_non_negatable = false
|
||||
) {
|
||||
$if_types = [];
|
||||
|
||||
if ($conditional instanceof PhpParser\Node\Expr\Instanceof_) {
|
||||
@ -283,6 +289,7 @@ class TypeChecker
|
||||
$null_position = self::hasNullVariable($conditional);
|
||||
$false_position = self::hasFalseVariable($conditional);
|
||||
$gettype_position = self::hasGetTypeCheck($conditional);
|
||||
$scalar_value_position = $allow_non_negatable ? self::hasScalarValueComparison($conditional) : false;
|
||||
|
||||
$var_name = null;
|
||||
|
||||
@ -354,6 +361,42 @@ class TypeChecker
|
||||
$var_type = $conditional->right->value;
|
||||
}
|
||||
|
||||
if ($var_name && $var_type) {
|
||||
$if_types[$var_name] = $var_type;
|
||||
}
|
||||
}
|
||||
elseif ($scalar_value_position) {
|
||||
$var_type = null;
|
||||
|
||||
if ($scalar_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
||||
/** @var PhpParser\Node\Expr $conditional->right */
|
||||
$var_name = ExpressionChecker::getArrayVarId($conditional->left, $this_class_name, $namespace, $aliased_classes);
|
||||
|
||||
if ($conditional->right instanceof PhpParser\Node\Scalar\String_) {
|
||||
$var_type = 'string';
|
||||
}
|
||||
elseif ($conditional->right instanceof PhpParser\Node\Scalar\LNumber) {
|
||||
$var_type = 'int';
|
||||
}
|
||||
elseif ($conditional->right instanceof PhpParser\Node\Scalar\DNumber) {
|
||||
$var_type = 'float';
|
||||
}
|
||||
}
|
||||
else if ($scalar_value_position === self::ASSIGNMENT_TO_LEFT) {
|
||||
/** @var PhpParser\Node\Expr $conditional->left */
|
||||
$var_name = ExpressionChecker::getArrayVarId($conditional->right, $this_class_name, $namespace, $aliased_classes);
|
||||
|
||||
if ($conditional->left instanceof PhpParser\Node\Scalar\String_) {
|
||||
$var_type = 'string';
|
||||
}
|
||||
elseif ($conditional->left instanceof PhpParser\Node\Scalar\LNumber) {
|
||||
$var_type = 'int';
|
||||
}
|
||||
elseif ($conditional->left instanceof PhpParser\Node\Scalar\DNumber) {
|
||||
$var_type = 'float';
|
||||
}
|
||||
}
|
||||
|
||||
if ($var_name && $var_type) {
|
||||
$if_types[$var_name] = $var_type;
|
||||
}
|
||||
@ -631,6 +674,26 @@ class TypeChecker
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected static function hasScalarValueComparison(PhpParser\Node\Expr\BinaryOp $conditional)
|
||||
{
|
||||
if (!$conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($conditional->right instanceof PhpParser\Node\Scalar) {
|
||||
return self::ASSIGNMENT_TO_RIGHT;
|
||||
}
|
||||
|
||||
if ($conditional->left instanceof PhpParser\Node\Scalar) {
|
||||
return self::ASSIGNMENT_TO_LEFT;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
|
@ -193,6 +193,10 @@ class Config
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<\SimpleXMLElement> $extensions
|
||||
* @return void
|
||||
*/
|
||||
protected function loadFileExtensions($extensions)
|
||||
{
|
||||
foreach ($extensions as $extension) {
|
||||
|
@ -44,28 +44,28 @@ class FileFilter
|
||||
|
||||
if ($e->directory) {
|
||||
foreach ($e->directory as $directory) {
|
||||
$filter->include_dirs[] = self::slashify($directory['name']);
|
||||
$filter->include_dirs[] = self::slashify((string)$directory['name']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($e->file) {
|
||||
foreach ($e->file as $file) {
|
||||
$filter->include_files[] = $file['name'];
|
||||
$filter->include_files_lowercase[] = strtolower($file['name']);
|
||||
$filter->include_files_lowercase[] = strtolower((string)$file['name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($e->directory) {
|
||||
foreach ($e->directory as $directory) {
|
||||
$filter->exclude_dirs[] = self::slashify($directory['name']);
|
||||
$filter->exclude_dirs[] = self::slashify((string)$directory['name']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($e->file) {
|
||||
foreach ($e->file as $file) {
|
||||
$filter->exclude_files[] = (string)$file['name'];
|
||||
$filter->exclude_files_lowercase[] = strtolower($file['name']);
|
||||
$filter->exclude_files_lowercase[] = strtolower((string)$file['name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -73,11 +73,20 @@ class FileFilter
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
protected static function slashify($str)
|
||||
{
|
||||
return preg_replace('/\/?$/', '/', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file_name
|
||||
* @param boolean $case_sensitive
|
||||
* @return boolean
|
||||
*/
|
||||
public function allows($file_name, $case_sensitive = false)
|
||||
{
|
||||
if ($this->inclusive) {
|
||||
|
@ -137,6 +137,10 @@ class Context
|
||||
return $redefined_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $remove_var_id
|
||||
* @return void
|
||||
*/
|
||||
public function remove($remove_var_id)
|
||||
{
|
||||
if (isset($this->vars_in_scope[$remove_var_id])) {
|
||||
@ -147,6 +151,11 @@ class Context
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $remove_var_id
|
||||
* @param \Psalm\Type\Union|null $type
|
||||
* @return void
|
||||
*/
|
||||
public function removeDescendents($remove_var_id, \Psalm\Type\Union $type = null)
|
||||
{
|
||||
if (!$type && isset($this->vars_in_scope[$remove_var_id])) {
|
||||
|
@ -107,7 +107,9 @@ class EffectsAnalyser
|
||||
if ($collapse_types) {
|
||||
// if it's a generator, boil everything down to a single generator return type
|
||||
if ($yield_types) {
|
||||
/** @var Type\Union */
|
||||
$key_type = null;
|
||||
/** @var Type\Union */
|
||||
$value_type = null;
|
||||
|
||||
foreach ($yield_types as $type) {
|
||||
|
7
src/Psalm/Issue/UnimplementedInterfaceMethod.php
Normal file
7
src/Psalm/Issue/UnimplementedInterfaceMethod.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Issue;
|
||||
|
||||
class UnimplementedInterfaceMethod extends CodeError
|
||||
{
|
||||
}
|
@ -79,6 +79,10 @@ class IssueBuffer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @return bool
|
||||
*/
|
||||
protected static function alreadyEmitted($message)
|
||||
{
|
||||
$sham = sha1($message);
|
||||
|
@ -73,6 +73,10 @@ abstract class Type
|
||||
|
||||
private static function getTypeFromTree(ParseTree $parse_tree)
|
||||
{
|
||||
if (!$parse_tree->value) {
|
||||
throw new \InvalidArgumentException('Parse tree must have a value');
|
||||
}
|
||||
|
||||
if ($parse_tree->value === ParseTree::GENERIC) {
|
||||
$generic_type = array_shift($parse_tree->children);
|
||||
|
||||
@ -84,6 +88,10 @@ abstract class Type
|
||||
$parse_tree->children
|
||||
);
|
||||
|
||||
if (!$generic_type->value) {
|
||||
throw new \InvalidArgumentException('Generic type must have a value');
|
||||
}
|
||||
|
||||
$generic_type_value = self::fixScalarTerms($generic_type->value);
|
||||
|
||||
if (($generic_type_value === 'array' || $generic_type_value === 'Generator') && count($generic_params) === 1) {
|
||||
@ -125,7 +133,11 @@ abstract class Type
|
||||
$properties[$property_branch->children[0]->value] = $property_type;
|
||||
}
|
||||
|
||||
return new ObjectLike($type, $properties);
|
||||
if (!$type->value) {
|
||||
throw new \InvalidArgumentException('Object-like type must have a value');
|
||||
}
|
||||
|
||||
return new ObjectLike($type->value, $properties);
|
||||
}
|
||||
|
||||
$atomic_type = self::fixScalarTerms($parse_tree->value);
|
||||
@ -138,6 +150,7 @@ abstract class Type
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $return_type
|
||||
* @return array<int,string>
|
||||
*/
|
||||
public static function tokenize($return_type)
|
||||
@ -170,15 +183,18 @@ abstract class Type
|
||||
return $return_type_tokens;
|
||||
}
|
||||
|
||||
/** @return string */
|
||||
/**
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
public static function convertSquareBrackets($type)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/([a-zA-Z\<\>\\\\_\(\)|]+)((\[\])+)/',
|
||||
function ($matches) {
|
||||
$inner_type = str_replace(['(', ')'], '', $matches[1]);
|
||||
$inner_type = str_replace(['(', ')'], '', (string)$matches[1]);
|
||||
|
||||
$dimensionality = strlen($matches[2]) / 2;
|
||||
$dimensionality = strlen((string)$matches[2]) / 2;
|
||||
|
||||
for ($i = 0; $i < $dimensionality; $i++) {
|
||||
$inner_type = 'array<mixed, ' . $inner_type . '>';
|
||||
@ -298,54 +314,6 @@ abstract class Type
|
||||
return new Union([$type]);
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isMixed()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'mixed';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return isset($this->types['mixed']);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isNull()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'null';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return count($this->types) === 1 && isset($this->types['null']);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isVoid()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'void';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return isset($this->types['void']);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isEmpty()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'empty';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return isset($this->types['empty']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Union> $redefined_vars
|
||||
* @param Context $context
|
||||
@ -433,6 +401,7 @@ abstract class Type
|
||||
throw new \InvalidArgumentException('You must pass at least one type to combineTypes');
|
||||
}
|
||||
|
||||
/** @var array<string,array<string,Union>> */
|
||||
$key_types = [];
|
||||
|
||||
/** @var array<string,array<string,Union>> */
|
||||
@ -478,6 +447,7 @@ abstract class Type
|
||||
}
|
||||
elseif ($type instanceof ObjectLike) {
|
||||
if (!isset($value_types['object-like'])) {
|
||||
/** @var array<string,Union> */
|
||||
$value_types['object-like'] = [];
|
||||
}
|
||||
|
||||
|
@ -148,4 +148,28 @@ class Atomic extends Type
|
||||
{
|
||||
return $this->value === 'Generator';
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isMixed()
|
||||
{
|
||||
return $this->value === 'mixed';
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isNull()
|
||||
{
|
||||
return $this->value === 'null';
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isVoid()
|
||||
{
|
||||
return $this->value === 'void';
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isEmpty()
|
||||
{
|
||||
return $this->value === 'empty';
|
||||
}
|
||||
}
|
||||
|
@ -139,6 +139,36 @@ class Union extends Type
|
||||
return isset($this->types['int']) && count($this->types) === 1;
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isMixed()
|
||||
{
|
||||
return isset($this->types['mixed']);
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isNull()
|
||||
{
|
||||
return count($this->types) === 1 && isset($this->types['null']);
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isVoid()
|
||||
{
|
||||
return isset($this->types['void']);
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isEmpty()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'empty';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return isset($this->types['empty']);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeObjects()
|
||||
{
|
||||
foreach ($this->types as $key => $type) {
|
||||
|
Loading…
Reference in New Issue
Block a user