mirror of
https://github.com/danog/psalm.git
synced 2024-12-02 09:37:59 +01:00
Add TypeDoesNotContainType issue and fix those issues in Psalm code
This commit is contained in:
parent
73b1ab1411
commit
562f71b21f
@ -11,7 +11,7 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.5",
|
"php": ">=5.5",
|
||||||
"nikic/PHP-Parser": ">=3.0.1"
|
"nikic/PHP-Parser": ">=3.0.2"
|
||||||
},
|
},
|
||||||
"bin": ["bin/psalm"],
|
"bin": ["bin/psalm"],
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
14
composer.lock
generated
14
composer.lock
generated
@ -4,21 +4,21 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"hash": "11441a18f0cfe84a623e41d06913af49",
|
"hash": "388130e1e8fbae6b9998981593ede881",
|
||||||
"content-hash": "1922cc6635c63bfa5cf5efac8b57e44a",
|
"content-hash": "dc7c43d263dbc2c21eb204be11c4b80e",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "nikic/php-parser",
|
"name": "nikic/php-parser",
|
||||||
"version": "v3.0.1",
|
"version": "v3.0.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||||
"reference": "aa6aec90e11a7a4e7d44129c7cb5422ffd15939e"
|
"reference": "adf44419c0fc014a0f191db6f89d3e55d4211744"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/aa6aec90e11a7a4e7d44129c7cb5422ffd15939e",
|
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/adf44419c0fc014a0f191db6f89d3e55d4211744",
|
||||||
"reference": "aa6aec90e11a7a4e7d44129c7cb5422ffd15939e",
|
"reference": "adf44419c0fc014a0f191db6f89d3e55d4211744",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -56,7 +56,7 @@
|
|||||||
"parser",
|
"parser",
|
||||||
"php"
|
"php"
|
||||||
],
|
],
|
||||||
"time": "2016-12-01 12:37:30"
|
"time": "2016-12-06 11:30:35"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
|
@ -229,6 +229,7 @@ class CommentChecker
|
|||||||
$line_number++;
|
$line_number++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @var int|false */
|
||||||
$last = false;
|
$last = false;
|
||||||
foreach ($lines as $k => $line) {
|
foreach ($lines as $k => $line) {
|
||||||
if (preg_match('/^\s?@\w/i', $line)) {
|
if (preg_match('/^\s?@\w/i', $line)) {
|
||||||
|
@ -7,6 +7,7 @@ use PhpParser\Node\Stmt\Function_;
|
|||||||
use PhpParser;
|
use PhpParser;
|
||||||
use Psalm\CodeLocation;
|
use Psalm\CodeLocation;
|
||||||
use Psalm\Checker\Statements\ExpressionChecker;
|
use Psalm\Checker\Statements\ExpressionChecker;
|
||||||
|
use Psalm\Checker\TypeChecker;
|
||||||
use Psalm\Context;
|
use Psalm\Context;
|
||||||
use Psalm\EffectsAnalyser;
|
use Psalm\EffectsAnalyser;
|
||||||
use Psalm\Exception\DocblockParseException;
|
use Psalm\Exception\DocblockParseException;
|
||||||
@ -1013,134 +1014,6 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
|||||||
return implode('', $return_type_tokens);
|
return implode('', $return_type_tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the input param type match the given param type
|
|
||||||
*
|
|
||||||
* @param Type\Union $input_type
|
|
||||||
* @param Type\Union $param_type
|
|
||||||
* @param bool &$has_scalar_match
|
|
||||||
* @param bool &$type_coerced whether or not there was type coercion involved
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public static function doesParamMatch(
|
|
||||||
Type\Union $input_type,
|
|
||||||
Type\Union $param_type,
|
|
||||||
&$has_scalar_match = null,
|
|
||||||
&$type_coerced = null
|
|
||||||
) {
|
|
||||||
$has_scalar_match = true;
|
|
||||||
|
|
||||||
if ($param_type->isMixed()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$type_match_found = false;
|
|
||||||
$has_type_mismatch = false;
|
|
||||||
|
|
||||||
foreach ($input_type->types as $input_type_part) {
|
|
||||||
if ($input_type_part->isNull()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$type_match_found = false;
|
|
||||||
$scalar_type_match_found = false;
|
|
||||||
|
|
||||||
foreach ($param_type->types as $param_type_part) {
|
|
||||||
if ($param_type_part->isNull()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($input_type_part->value === $param_type_part->value ||
|
|
||||||
ClassChecker::classExtendsOrImplements($input_type_part->value, $param_type_part->value) ||
|
|
||||||
ExpressionChecker::isMock($input_type_part->value)
|
|
||||||
) {
|
|
||||||
$type_match_found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($input_type_part->value === 'false' && $param_type_part->value === 'bool') {
|
|
||||||
$type_match_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($input_type_part->value === 'int' && $param_type_part->value === 'float') {
|
|
||||||
$type_match_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($input_type_part->value === 'Closure' && $param_type_part->value === 'callable') {
|
|
||||||
$type_match_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($param_type_part->isNumeric() && $input_type_part->isNumericType()) {
|
|
||||||
$type_match_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($param_type_part->isGenericArray() && $input_type_part->isObjectLike()) {
|
|
||||||
$type_match_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($param_type_part->isIterable() &&
|
|
||||||
(
|
|
||||||
$input_type_part->isArray() ||
|
|
||||||
ClassChecker::classExtendsOrImplements($input_type_part->value, 'Traversable')
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
$type_match_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($param_type_part->isScalar() && $input_type_part->isScalarType()) {
|
|
||||||
$type_match_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($param_type_part->isString() && $input_type_part->isObjectType()) {
|
|
||||||
// check whether the object has a __toString method
|
|
||||||
if (MethodChecker::methodExists($input_type_part->value . '::__toString')) {
|
|
||||||
$type_match_found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($param_type_part->isCallable() &&
|
|
||||||
($input_type_part->value === 'string' || $input_type_part->value === 'array')
|
|
||||||
) {
|
|
||||||
// @todo add value checks if possible here
|
|
||||||
$type_match_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($input_type_part->isNumeric()) {
|
|
||||||
if ($param_type_part->isNumericType()) {
|
|
||||||
$scalar_type_match_found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($input_type_part->isScalarType() || $input_type_part->isScalar()) {
|
|
||||||
if ($param_type_part->isScalarType()) {
|
|
||||||
$scalar_type_match_found = true;
|
|
||||||
}
|
|
||||||
} elseif ($param_type_part->isObject() &&
|
|
||||||
!$input_type_part->isArray() &&
|
|
||||||
!$input_type_part->isResource()
|
|
||||||
) {
|
|
||||||
$type_match_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ClassChecker::classExtendsOrImplements($param_type_part->value, $input_type_part->value)) {
|
|
||||||
$type_coerced = true;
|
|
||||||
$type_match_found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$type_match_found) {
|
|
||||||
if (!$scalar_type_match_found) {
|
|
||||||
$has_scalar_match = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $method_id
|
* @param string $method_id
|
||||||
* @param array<PhpParser\Node\Arg> $args
|
* @param array<PhpParser\Node\Arg> $args
|
||||||
@ -1217,7 +1090,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FunctionLikeChecker::doesParamMatch($arg->value->inferredType, $param_type)) {
|
if (TypeChecker::isContainedBy($arg->value->inferredType, $param_type)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,9 +8,9 @@ class ScopeChecker
|
|||||||
/**
|
/**
|
||||||
* Do all code paths in this list of statements exit the block (return/throw)
|
* Do all code paths in this list of statements exit the block (return/throw)
|
||||||
*
|
*
|
||||||
* @param array<PhpParser\Node\Stmt> $stmts
|
* @param array<PhpParser\Node\Stmt|PhpParser\Node\Expr> $stmts
|
||||||
* @param bool $check_continue - also looks for a continue
|
* @param bool $check_continue - also looks for a continue
|
||||||
* @param bool $check_break
|
* @param bool $check_break
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function doesLeaveBlock(array $stmts, $check_continue = true, $check_break = true)
|
public static function doesLeaveBlock(array $stmts, $check_continue = true, $check_break = true)
|
||||||
|
@ -175,10 +175,6 @@ class ForeachChecker
|
|||||||
$statements_checker->registerVariable('$' . $stmt->keyVar->name, $stmt->getLine());
|
$statements_checker->registerVariable('$' . $stmt->keyVar->name, $stmt->getLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value_type && $value_type instanceof Type\Atomic) {
|
|
||||||
$value_type = new Type\Union([$value_type]);
|
|
||||||
}
|
|
||||||
|
|
||||||
AssignmentChecker::check(
|
AssignmentChecker::check(
|
||||||
$statements_checker,
|
$statements_checker,
|
||||||
$stmt->valueVar,
|
$stmt->valueVar,
|
||||||
|
@ -272,7 +272,7 @@ class IfChecker
|
|||||||
|
|
||||||
// if we have a check like if (!isset($a)) { $a = true; } we want to make sure $a is always set
|
// if we have a check like if (!isset($a)) { $a = true; } we want to make sure $a is always set
|
||||||
foreach ($if_scope->new_vars as $var_id => $type) {
|
foreach ($if_scope->new_vars as $var_id => $type) {
|
||||||
if (isset($if_scope->negated_types[$var_id]) && $if_scope->negated_types[$var_id] === '!null') {
|
if (isset($if_scope->negated_types[$var_id]) && $if_scope->negated_types[$var_id] === '!isset') {
|
||||||
$if_scope->forced_new_vars[$var_id] = Type::getMixed();
|
$if_scope->forced_new_vars[$var_id] = Type::getMixed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ use Psalm\Checker\MethodChecker;
|
|||||||
use Psalm\Checker\StatementsChecker;
|
use Psalm\Checker\StatementsChecker;
|
||||||
use Psalm\Checker\Statements\ExpressionChecker;
|
use Psalm\Checker\Statements\ExpressionChecker;
|
||||||
use Psalm\Checker\TraitChecker;
|
use Psalm\Checker\TraitChecker;
|
||||||
|
use Psalm\Checker\TypeChecker;
|
||||||
use Psalm\CodeLocation;
|
use Psalm\CodeLocation;
|
||||||
use Psalm\Context;
|
use Psalm\Context;
|
||||||
use Psalm\Issue\ForbiddenCode;
|
use Psalm\Issue\ForbiddenCode;
|
||||||
@ -1013,9 +1014,10 @@ class CallChecker
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$type_match_found = FunctionLikeChecker::doesParamMatch(
|
$type_match_found = TypeChecker::isContainedBy(
|
||||||
$input_type,
|
$input_type,
|
||||||
$closure_param_type,
|
$closure_param_type,
|
||||||
|
false,
|
||||||
$scalar_type_match_found,
|
$scalar_type_match_found,
|
||||||
$coerced_type
|
$coerced_type
|
||||||
);
|
);
|
||||||
@ -1150,9 +1152,10 @@ class CallChecker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$type_match_found = FunctionLikeChecker::doesParamMatch(
|
$type_match_found = TypeChecker::isContainedBy(
|
||||||
$input_type,
|
$input_type,
|
||||||
$param_type,
|
$param_type,
|
||||||
|
true,
|
||||||
$scalar_type_match_found,
|
$scalar_type_match_found,
|
||||||
$coerced_type
|
$coerced_type
|
||||||
);
|
);
|
||||||
|
@ -5,6 +5,7 @@ use PhpParser;
|
|||||||
use Psalm\Checker\Statements\ExpressionChecker;
|
use Psalm\Checker\Statements\ExpressionChecker;
|
||||||
use Psalm\CodeLocation;
|
use Psalm\CodeLocation;
|
||||||
use Psalm\Issue\FailedTypeResolution;
|
use Psalm\Issue\FailedTypeResolution;
|
||||||
|
use Psalm\Issue\TypeDoesNotContainType;
|
||||||
use Psalm\IssueBuffer;
|
use Psalm\IssueBuffer;
|
||||||
use Psalm\Type;
|
use Psalm\Type;
|
||||||
|
|
||||||
@ -348,7 +349,7 @@ class TypeChecker
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($var_name) {
|
if ($var_name) {
|
||||||
if ($conditional->expr instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
|
if ($conditional->expr instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
||||||
$if_types[$var_name] = 'false';
|
$if_types[$var_name] = 'false';
|
||||||
} else {
|
} else {
|
||||||
// we do this because == null gives us a weaker idea than === null
|
// we do this because == null gives us a weaker idea than === null
|
||||||
@ -386,7 +387,7 @@ class TypeChecker
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($var_name) {
|
if ($var_name) {
|
||||||
$if_types[$var_name] = 'null';
|
$if_types[$var_name] = 'isset';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -598,7 +599,7 @@ class TypeChecker
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($var_name) {
|
if ($var_name) {
|
||||||
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
||||||
$if_types[$var_name] = '!false';
|
$if_types[$var_name] = '!false';
|
||||||
} else {
|
} else {
|
||||||
$if_types[$var_name] = '!empty';
|
$if_types[$var_name] = '!empty';
|
||||||
@ -654,7 +655,7 @@ class TypeChecker
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($var_name) {
|
if ($var_name) {
|
||||||
$if_types[$var_name] = '!null';
|
$if_types[$var_name] = '!isset';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1162,10 +1163,6 @@ class TypeChecker
|
|||||||
return $existing_var_type;
|
return $existing_var_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($new_var_type === 'null') {
|
|
||||||
return Type::getNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($new_var_type[0] === '!') {
|
if ($new_var_type[0] === '!') {
|
||||||
if ($new_var_type === '!object' && !$existing_var_type->isMixed()) {
|
if ($new_var_type === '!object' && !$existing_var_type->isMixed()) {
|
||||||
$non_object_types = [];
|
$non_object_types = [];
|
||||||
@ -1181,7 +1178,7 @@ class TypeChecker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array($new_var_type, ['!empty', '!null'])) {
|
if (in_array($new_var_type, ['!empty', '!null', '!isset'])) {
|
||||||
$existing_var_type->removeType('null');
|
$existing_var_type->removeType('null');
|
||||||
|
|
||||||
if ($new_var_type === '!empty') {
|
if ($new_var_type === '!empty') {
|
||||||
@ -1246,7 +1243,159 @@ class TypeChecker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Type::parseString($new_var_type);
|
if ($new_var_type === 'isset') {
|
||||||
|
return Type::getNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_type = Type::parseString($new_var_type);
|
||||||
|
|
||||||
|
if ($existing_var_type->isMixed()) {
|
||||||
|
return $new_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TypeChecker::isContainedBy($new_type, $existing_var_type) && $code_location) {
|
||||||
|
if (IssueBuffer::accepts(
|
||||||
|
new TypeDoesNotContainType(
|
||||||
|
'Cannot resolve types for ' . $key . ' - ' . $existing_var_type . ' does not contain ' . $new_type,
|
||||||
|
$code_location
|
||||||
|
),
|
||||||
|
$suppressed_issues
|
||||||
|
)) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $new_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the input param type match the given param type
|
||||||
|
*
|
||||||
|
* @param Type\Union $input_type
|
||||||
|
* @param Type\Union $container_type
|
||||||
|
* @param bool $ignore_null
|
||||||
|
* @param bool &$has_scalar_match
|
||||||
|
* @param bool &$type_coerced whether or not there was type coercion involved
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isContainedBy(
|
||||||
|
Type\Union $input_type,
|
||||||
|
Type\Union $container_type,
|
||||||
|
$ignore_null = false,
|
||||||
|
&$has_scalar_match = null,
|
||||||
|
&$type_coerced = null
|
||||||
|
) {
|
||||||
|
$has_scalar_match = true;
|
||||||
|
|
||||||
|
if ($container_type->isMixed()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type_match_found = false;
|
||||||
|
$has_type_mismatch = false;
|
||||||
|
|
||||||
|
foreach ($input_type->types as $input_type_part) {
|
||||||
|
if ($input_type_part->isNull() && $ignore_null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type_match_found = false;
|
||||||
|
$scalar_type_match_found = false;
|
||||||
|
|
||||||
|
foreach ($container_type->types as $container_type_part) {
|
||||||
|
if ($container_type_part->isNull() && $ignore_null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input_type_part->value === $container_type_part->value ||
|
||||||
|
ClassChecker::classExtendsOrImplements($input_type_part->value, $container_type_part->value) ||
|
||||||
|
ExpressionChecker::isMock($input_type_part->value)
|
||||||
|
) {
|
||||||
|
$type_match_found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input_type_part->value === 'false' && $container_type_part->value === 'bool') {
|
||||||
|
$type_match_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input_type_part->value === 'int' && $container_type_part->value === 'float') {
|
||||||
|
$type_match_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input_type_part->value === 'Closure' && $container_type_part->value === 'callable') {
|
||||||
|
$type_match_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($container_type_part->isNumeric() && $input_type_part->isNumericType()) {
|
||||||
|
$type_match_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($container_type_part->isGenericArray() && $input_type_part->isObjectLike()) {
|
||||||
|
$type_match_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($container_type_part->isIterable() &&
|
||||||
|
(
|
||||||
|
$input_type_part->isArray() ||
|
||||||
|
ClassChecker::classExtendsOrImplements($input_type_part->value, 'Traversable')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
$type_match_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($container_type_part->isScalar() && $input_type_part->isScalarType()) {
|
||||||
|
$type_match_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($container_type_part->isString() && $input_type_part->isObjectType()) {
|
||||||
|
// check whether the object has a __toString method
|
||||||
|
if (MethodChecker::methodExists($input_type_part->value . '::__toString')) {
|
||||||
|
$type_match_found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($container_type_part->isCallable() &&
|
||||||
|
($input_type_part->value === 'string' || $input_type_part->value === 'array')
|
||||||
|
) {
|
||||||
|
// @todo add value checks if possible here
|
||||||
|
$type_match_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input_type_part->isNumeric()) {
|
||||||
|
if ($container_type_part->isNumericType()) {
|
||||||
|
$scalar_type_match_found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input_type_part->isScalarType() || $input_type_part->isScalar()) {
|
||||||
|
if ($container_type_part->isScalarType()) {
|
||||||
|
$scalar_type_match_found = true;
|
||||||
|
}
|
||||||
|
} elseif ($container_type_part->isObject() &&
|
||||||
|
!$input_type_part->isArray() &&
|
||||||
|
!$input_type_part->isResource()
|
||||||
|
) {
|
||||||
|
$type_match_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ClassChecker::classExtendsOrImplements($container_type_part->value, $input_type_part->value)) {
|
||||||
|
$type_coerced = true;
|
||||||
|
$type_match_found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$type_match_found) {
|
||||||
|
if (!$scalar_type_match_found) {
|
||||||
|
$has_scalar_match = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1273,6 +1422,7 @@ class TypeChecker
|
|||||||
$new_base_key = $base_key . '->' . $key_parts[$i];
|
$new_base_key = $base_key . '->' . $key_parts[$i];
|
||||||
|
|
||||||
if (!isset($existing_keys[$new_base_key])) {
|
if (!isset($existing_keys[$new_base_key])) {
|
||||||
|
/** @var null|Type\Union */
|
||||||
$new_base_type = null;
|
$new_base_type = null;
|
||||||
|
|
||||||
foreach ($existing_keys[$base_key]->types as $existing_key_type_part) {
|
foreach ($existing_keys[$base_key]->types as $existing_key_type_part) {
|
||||||
|
@ -12,9 +12,9 @@ class EffectsAnalyser
|
|||||||
/**
|
/**
|
||||||
* Gets the return types from a list of statements
|
* Gets the return types from a list of statements
|
||||||
*
|
*
|
||||||
* @param array<int,PhpParser\Node\Stmt> $stmts
|
* @param array<int,PhpParser\Node\Stmt|PhpParser\Node\Expr> $stmts
|
||||||
* @param array<int,Type\Atomic> $yield_types
|
* @param array<int,Type\Atomic> $yield_types
|
||||||
* @param bool $collapse_types
|
* @param bool $collapse_types
|
||||||
* @return array<int,Type\Atomic> a list of return types
|
* @return array<int,Type\Atomic> a list of return types
|
||||||
*/
|
*/
|
||||||
public static function getReturnTypes(array $stmts, array &$yield_types, $collapse_types = false)
|
public static function getReturnTypes(array $stmts, array &$yield_types, $collapse_types = false)
|
||||||
@ -92,7 +92,7 @@ class EffectsAnalyser
|
|||||||
/** @var Type\Union */
|
/** @var Type\Union */
|
||||||
$key_type = null;
|
$key_type = null;
|
||||||
|
|
||||||
/** @var Type\Union */
|
/** @var Type\Union|null */
|
||||||
$value_type = null;
|
$value_type = null;
|
||||||
|
|
||||||
foreach ($yield_types as $type) {
|
foreach ($yield_types as $type) {
|
||||||
|
6
src/Psalm/Issue/TypeDoesNotContainType.php
Normal file
6
src/Psalm/Issue/TypeDoesNotContainType.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
namespace Psalm\Issue;
|
||||||
|
|
||||||
|
class TypeDoesNotContainType extends CodeError
|
||||||
|
{
|
||||||
|
}
|
@ -64,7 +64,7 @@ class PropertyTypeTest extends PHPUnit_Framework_TestCase
|
|||||||
public $foo;
|
public $foo;
|
||||||
}
|
}
|
||||||
|
|
||||||
$a = null;
|
$a = rand(0, 10) ? new A() : (rand(0, 10) ? new B() : null);
|
||||||
$b = null;
|
$b = null;
|
||||||
|
|
||||||
if ($a instanceof A || $a instanceof B) {
|
if ($a instanceof A || $a instanceof B) {
|
||||||
@ -90,7 +90,7 @@ class PropertyTypeTest extends PHPUnit_Framework_TestCase
|
|||||||
public $foo;
|
public $foo;
|
||||||
}
|
}
|
||||||
|
|
||||||
$a = null;
|
$a = rand(0, 10) ? new A() : new B();
|
||||||
$b = null;
|
$b = null;
|
||||||
|
|
||||||
if (rand(0, 10) === 4) {
|
if (rand(0, 10) === 4) {
|
||||||
|
@ -88,16 +88,6 @@ class TypeReconciliationTest extends PHPUnit_Framework_TestCase
|
|||||||
(string) TypeChecker::reconcileTypes('null', Type::parseString('MyObject|null'))
|
(string) TypeChecker::reconcileTypes('null', Type::parseString('MyObject|null'))
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
'null',
|
|
||||||
(string) TypeChecker::reconcileTypes('null', Type::parseString('MyObject'))
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
'null',
|
|
||||||
(string) TypeChecker::reconcileTypes('null', Type::parseString('MyObject|false'))
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'null',
|
'null',
|
||||||
(string) TypeChecker::reconcileTypes('null', Type::parseString('mixed'))
|
(string) TypeChecker::reconcileTypes('null', Type::parseString('mixed'))
|
||||||
@ -161,12 +151,22 @@ class TypeReconciliationTest extends PHPUnit_Framework_TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAllMixed()
|
/**
|
||||||
|
* @expectedException \Psalm\Exception\CodeException
|
||||||
|
* @expectedExceptionMessage TypeDoesNotContainType
|
||||||
|
*/
|
||||||
|
public function testMakeNonNullableNull()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$stmts = self::$parser->parse('<?php
|
||||||
'mixed',
|
class A { }
|
||||||
(string) TypeChecker::reconcileTypes('mixed', Type::parseString('mixed'))
|
$a = new A();
|
||||||
);
|
if ($a === null) {
|
||||||
|
}
|
||||||
|
');
|
||||||
|
|
||||||
|
$file_checker = new FileChecker('somefile.php', $stmts);
|
||||||
|
$context = new Context('somefile.php');
|
||||||
|
$file_checker->check(true, true, $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNotInstanceOf()
|
public function testNotInstanceOf()
|
||||||
|
Loading…
Reference in New Issue
Block a user