mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
parent
20f7147af1
commit
ea89a6ba29
@ -37,8 +37,6 @@
|
||||
<xs:attribute name="allowPhpStormGenerics" type="xs:string" />
|
||||
<xs:attribute name="allowCoercionFromStringToClassConst" type="xs:string" />
|
||||
<xs:attribute name="allowStringToStandInForClass" type="xs:string" />
|
||||
<xs:attribute name="ignoreInternalFunctionFalseReturn" type="xs:string" />
|
||||
<xs:attribute name="ignoreInternalFunctionNullReturn" type="xs:string" />
|
||||
<xs:attribute name="usePhpDocMethodsWithoutMagicCall" type="xs:string" />
|
||||
<xs:attribute name="memoizeMethodCallResults" type="xs:string" />
|
||||
<xs:attribute name="hoistConstants" type="xs:string" />
|
||||
|
@ -36,7 +36,7 @@ Psalm uses an XML config file. A barebones example looks like this:
|
||||
- `allowCoercionFromStringToClassConst=[bool]`<br />
|
||||
When `true`, strings can be coerced to [`class-string`](supported_annotations.md#class-constants), with Psalm emitting a `TypeCoercion` issue. If disabled, that issue changes to a more serious one. Defaults to `true`.
|
||||
- `allowStringToStandInForClass=[bool]`<br />
|
||||
When `true`, strings can be used as classes, meaning `$some_string::someMethod()` is allowed. If `false`, only class constant strings (of the form `Foo\Bar::class`) can stand in for classes, otherwise an `InvalidStringClass` issue is emitted. Defaults to `false`.
|
||||
When `true`, strings can be used as classes, meaning `$some_string::someMethod()` is allowed. If `false`, only class constant strings (of the form `Foo\Bar::class`) can stand in for classes, otherwise an `InvalidStringClass` issue is emitted. Defaults to `true` (no issues emitted).
|
||||
- `memoizeMethodCallResults=[bool]`<br />
|
||||
When `true`, the results of method calls without arguments passed arguments are remembered between repeated calls of that method on a given object. Defaults to `false`.
|
||||
- `hoistConstants=[bool]`<br />
|
||||
@ -45,10 +45,6 @@ Psalm uses an XML config file. A barebones example looks like this:
|
||||
Occasionally a param default will not match up with the docblock type. By default, Psalm emits an issue. Setting this flag to `true` causes it to expand the param type to include the param default. Defaults to `false`.
|
||||
- `checkForThrowsDocblock=[bool]`<br />
|
||||
When `true`, Psalm will check that the developer has supplied `@throws` docblocks for every exception thrown in a given function or method. Defaults to `false`.
|
||||
- `ignoreInternalFunctionFalseReturn=[bool]`<br />
|
||||
When `true`, Psalm ignores possibly-false issues stemming from return values of internal functions (like `preg_split`) that may return false, but do so rarely). Defaults to `true`.
|
||||
- `ignoreInternalFunctionNullReturn=[bool]`<br />
|
||||
When `true`, Psalm ignores possibly-null issues stemming from return values of internal array functions (like `current`) that may return null, but do so rarely. Defaults to `true`.
|
||||
|
||||
### Running Psalm
|
||||
|
||||
|
@ -214,16 +214,6 @@ class Config
|
||||
*/
|
||||
public $check_for_throws_docblock = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $ignore_internal_falsable_issues = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $ignore_internal_nullable_issues = true;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
@ -589,16 +579,6 @@ class Config
|
||||
$config->forbid_echo = $attribute_text === 'true' || $attribute_text === '1';
|
||||
}
|
||||
|
||||
if (isset($config_xml['ignoreRareInternalFunctionFalseReturn'])) {
|
||||
$attribute_text = (string) $config_xml['ignoreRareInternalFunctionFalseReturn'];
|
||||
$config->ignore_internal_falsable_issues = $attribute_text === 'true' || $attribute_text === '1';
|
||||
}
|
||||
|
||||
if (isset($config_xml['ignoreRareInternalFunctioNullReturn'])) {
|
||||
$attribute_text = (string) $config_xml['ignoreRareInternalFunctionNullReturn'];
|
||||
$config->ignore_internal_nullable_issues = $attribute_text === 'true' || $attribute_text === '1';
|
||||
}
|
||||
|
||||
if (isset($config_xml['errorBaseline'])) {
|
||||
$attribute_text = (string) $config_xml['errorBaseline'];
|
||||
$config->error_baseline = $attribute_text;
|
||||
|
@ -190,10 +190,6 @@ class CommentAnalyzer
|
||||
|
||||
$var_line_parts = preg_split('/( |=)/', $var_line, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
if (!$var_line_parts) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type_alias = array_shift($var_line_parts);
|
||||
|
||||
if (!isset($var_line_parts[0])) {
|
||||
|
@ -4,7 +4,6 @@ namespace Psalm\Internal\Analyzer;
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\AssertionFinder;
|
||||
use Psalm\Internal\Codebase\CallMap;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\Context;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Issue\InvalidArgument;
|
||||
@ -101,12 +100,7 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
|
||||
if (in_array($call_map_key, ['preg_replace', 'preg_replace_callback'], true)) {
|
||||
$return_type->addType(new Type\Atomic\TNull());
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($codebase->config->ignore_internal_nullable_issues) {
|
||||
$return_type->ignore_nullable_issues = true;
|
||||
}
|
||||
$return_type->ignore_nullable_issues = true;
|
||||
}
|
||||
|
||||
return $return_type;
|
||||
@ -121,13 +115,6 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
|
||||
return Type::getArray();
|
||||
|
||||
case 'current':
|
||||
case 'next':
|
||||
case 'prev':
|
||||
case 'reset':
|
||||
case 'end':
|
||||
return self::getArrayPointerAdjustReturn($call_args, $statements_analyzer->getCodebase());
|
||||
|
||||
case 'count':
|
||||
if (isset($call_args[0]->value->inferredType)) {
|
||||
$atomic_types = $call_args[0]->value->inferredType->getTypes();
|
||||
@ -255,10 +242,6 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
case 'array_slice':
|
||||
return self::getArraySliceReturnType($call_args);
|
||||
|
||||
case 'array_pop':
|
||||
case 'array_shift':
|
||||
return self::getArrayPopReturnType($call_args, $statements_analyzer->getCodebase());
|
||||
|
||||
case 'explode':
|
||||
if ($call_args[0]->value instanceof PhpParser\Node\Scalar\String_) {
|
||||
if ($call_args[0]->value->value === '') {
|
||||
@ -266,16 +249,7 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
}
|
||||
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TNonEmptyArray([
|
||||
Type::getInt(),
|
||||
Type::getString()
|
||||
])
|
||||
]);
|
||||
} elseif (isset($call_args[0]->value->inferredType)
|
||||
&& $call_args[0]->value->inferredType->hasString()
|
||||
) {
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TNonEmptyArray([
|
||||
new Type\Atomic\TArray([
|
||||
Type::getInt(),
|
||||
Type::getString()
|
||||
])
|
||||
@ -408,6 +382,8 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
$operator_type = $call_args[2]->value->inferredType;
|
||||
|
||||
if (!$operator_type->hasMixed()) {
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
$acceptable_operator_type = new Type\Union([
|
||||
new Type\Atomic\TLiteralString('<'),
|
||||
new Type\Atomic\TLiteralString('lt'),
|
||||
@ -425,8 +401,6 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
new Type\Atomic\TLiteralString('ne'),
|
||||
]);
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if (TypeAnalyzer::isContainedBy(
|
||||
$codebase,
|
||||
$operator_type,
|
||||
@ -481,11 +455,7 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
new Type\Atomic\TNull
|
||||
]);
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($codebase->config->ignore_internal_nullable_issues) {
|
||||
$nullable_string->ignore_nullable_issues = true;
|
||||
}
|
||||
$nullable_string->ignore_nullable_issues = true;
|
||||
|
||||
return $nullable_string;
|
||||
}
|
||||
@ -500,11 +470,7 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
new Type\Atomic\TNull
|
||||
]);
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($codebase->config->ignore_internal_nullable_issues) {
|
||||
$nullable_int->ignore_nullable_issues = true;
|
||||
}
|
||||
$nullable_int->ignore_nullable_issues = true;
|
||||
|
||||
return $nullable_int;
|
||||
}
|
||||
@ -517,11 +483,7 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
new Type\Atomic\TNull
|
||||
]);
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($codebase->config->ignore_internal_nullable_issues) {
|
||||
$nullable_string_or_int->ignore_nullable_issues = true;
|
||||
}
|
||||
$nullable_string_or_int->ignore_nullable_issues = true;
|
||||
|
||||
return $nullable_string_or_int;
|
||||
}
|
||||
@ -542,11 +504,7 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
new Type\Atomic\TFalse
|
||||
]);
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($codebase->config->ignore_internal_falsable_issues) {
|
||||
$nullable_string_or_int->ignore_falsable_issues = true;
|
||||
}
|
||||
$nullable_string_or_int->ignore_falsable_issues = true;
|
||||
|
||||
return $nullable_string_or_int;
|
||||
|
||||
@ -677,11 +635,7 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
break;
|
||||
|
||||
default:
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($call_map_return_type->isFalsable()
|
||||
&& $codebase->config->ignore_internal_falsable_issues
|
||||
) {
|
||||
if ($call_map_return_type->isFalsable()) {
|
||||
$call_map_return_type->ignore_falsable_issues = true;
|
||||
}
|
||||
}
|
||||
@ -689,43 +643,6 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
return $call_map_return_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<PhpParser\Node\Arg> $call_args
|
||||
*
|
||||
* @return Type\Union
|
||||
*/
|
||||
private static function getArrayPointerAdjustReturn(array $call_args, Codebase $codebase)
|
||||
{
|
||||
$first_arg = isset($call_args[0]->value) ? $call_args[0]->value : null;
|
||||
|
||||
$first_arg_array = $first_arg
|
||||
&& isset($first_arg->inferredType)
|
||||
&& $first_arg->inferredType->hasType('array')
|
||||
&& ($array_atomic_type = $first_arg->inferredType->getTypes()['array'])
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray ||
|
||||
$array_atomic_type instanceof Type\Atomic\ObjectLike)
|
||||
? $array_atomic_type
|
||||
: null;
|
||||
|
||||
if (!$first_arg_array) {
|
||||
return Type::getMixed();
|
||||
}
|
||||
|
||||
if ($first_arg_array instanceof Type\Atomic\TArray) {
|
||||
$value_type = clone $first_arg_array->type_params[1];
|
||||
} else {
|
||||
$value_type = $first_arg_array->getGenericValueType();
|
||||
}
|
||||
|
||||
$value_type->addType(new Type\Atomic\TFalse);
|
||||
|
||||
if ($codebase->config->ignore_internal_falsable_issues) {
|
||||
$value_type->ignore_falsable_issues = true;
|
||||
}
|
||||
|
||||
return $value_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<PhpParser\Node\Arg> $call_args
|
||||
*
|
||||
@ -922,59 +839,6 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
return new Type\Union([$first_arg_array->getGenericArrayType()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<PhpParser\Node\Arg> $call_args
|
||||
*
|
||||
* @return Type\Union
|
||||
*/
|
||||
private static function getArrayPopReturnType(array $call_args, Codebase $codebase)
|
||||
{
|
||||
$first_arg = isset($call_args[0]->value) ? $call_args[0]->value : null;
|
||||
|
||||
$first_arg_array = $first_arg
|
||||
&& isset($first_arg->inferredType)
|
||||
&& $first_arg->inferredType->hasType('array')
|
||||
&& ($array_atomic_type = $first_arg->inferredType->getTypes()['array'])
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray ||
|
||||
$array_atomic_type instanceof Type\Atomic\ObjectLike)
|
||||
? $array_atomic_type
|
||||
: null;
|
||||
|
||||
if (!$first_arg_array) {
|
||||
return Type::getMixed();
|
||||
}
|
||||
|
||||
$nullable = false;
|
||||
|
||||
if ($first_arg_array instanceof Type\Atomic\TArray) {
|
||||
$value_type = clone $first_arg_array->type_params[1];
|
||||
|
||||
if ($value_type->isEmpty()) {
|
||||
return Type::getNull();
|
||||
}
|
||||
|
||||
if (!$first_arg_array instanceof Type\Atomic\TNonEmptyArray) {
|
||||
$nullable = true;
|
||||
}
|
||||
} else {
|
||||
$value_type = $first_arg_array->getGenericValueType();
|
||||
|
||||
if (!$first_arg_array->sealed) {
|
||||
$nullable = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($nullable) {
|
||||
$value_type->addType(new Type\Atomic\TNull);
|
||||
|
||||
if ($codebase->config->ignore_internal_nullable_issues) {
|
||||
$value_type->ignore_nullable_issues = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $value_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<PhpParser\Node\Arg> $call_args
|
||||
* @param CodeLocation $code_location
|
||||
@ -1046,15 +910,6 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
]);
|
||||
}
|
||||
|
||||
if ($array_arg_type instanceof Type\Atomic\TNonEmptyArray) {
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TNonEmptyArray([
|
||||
$generic_key_type,
|
||||
$inner_type,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TArray([
|
||||
$generic_key_type,
|
||||
|
@ -123,36 +123,9 @@ class AssertionFinder
|
||||
return;
|
||||
}
|
||||
|
||||
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
|
||||
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual
|
||||
) {
|
||||
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional);
|
||||
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater) {
|
||||
$typed_value_position = self::hasTypedValueComparison($conditional);
|
||||
|
||||
if ($count_equality_position) {
|
||||
if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
|
||||
$count_expr = $conditional->left;
|
||||
} elseif ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
|
||||
$count_expr = $conditional->right;
|
||||
} else {
|
||||
throw new \UnexpectedValueException('$count_equality_position value');
|
||||
}
|
||||
|
||||
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
|
||||
$var_name = ExpressionAnalyzer::getArrayVarId(
|
||||
$count_expr->args[0]->value,
|
||||
$this_class_name,
|
||||
$source
|
||||
);
|
||||
|
||||
if ($var_name) {
|
||||
$if_types[$var_name] = [['=non-empty-countable']];
|
||||
}
|
||||
|
||||
$conditional->assertions = $if_types;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($typed_value_position) {
|
||||
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
||||
/** @var PhpParser\Node\Expr $conditional->right */
|
||||
@ -179,36 +152,9 @@ class AssertionFinder
|
||||
return;
|
||||
}
|
||||
|
||||
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
|
||||
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual
|
||||
) {
|
||||
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional);
|
||||
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller) {
|
||||
$typed_value_position = self::hasTypedValueComparison($conditional);
|
||||
|
||||
if ($count_equality_position) {
|
||||
if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
|
||||
$count_expr = $conditional->left;
|
||||
} elseif ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
|
||||
$count_expr = $conditional->right;
|
||||
} else {
|
||||
throw new \UnexpectedValueException('$count_equality_position value');
|
||||
}
|
||||
|
||||
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
|
||||
$var_name = ExpressionAnalyzer::getArrayVarId(
|
||||
$count_expr->args[0]->value,
|
||||
$this_class_name,
|
||||
$source
|
||||
);
|
||||
|
||||
if ($var_name) {
|
||||
$if_types[$var_name] = [['=non-empty-countable']];
|
||||
}
|
||||
|
||||
$conditional->assertions = $if_types;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($typed_value_position) {
|
||||
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
||||
$var_name = null;
|
||||
@ -366,7 +312,6 @@ class AssertionFinder
|
||||
$true_position = self::hasTrueVariable($conditional);
|
||||
$gettype_position = self::hasGetTypeCheck($conditional);
|
||||
$getclass_position = self::hasGetClassCheck($conditional);
|
||||
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional);
|
||||
$typed_value_position = self::hasTypedValueComparison($conditional);
|
||||
|
||||
if ($null_position !== null) {
|
||||
@ -653,30 +598,6 @@ class AssertionFinder
|
||||
return;
|
||||
}
|
||||
|
||||
if ($count_equality_position) {
|
||||
if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
|
||||
$count_expr = $conditional->left;
|
||||
} elseif ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
|
||||
$count_expr = $conditional->right;
|
||||
} else {
|
||||
throw new \UnexpectedValueException('$count_equality_position value');
|
||||
}
|
||||
|
||||
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
|
||||
$var_name = ExpressionAnalyzer::getArrayVarId(
|
||||
$count_expr->args[0]->value,
|
||||
$this_class_name,
|
||||
$source
|
||||
);
|
||||
|
||||
if ($var_name) {
|
||||
$if_types[$var_name] = [['=non-empty-countable']];
|
||||
}
|
||||
|
||||
$conditional->assertions = $if_types;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($getclass_position) {
|
||||
if ($getclass_position === self::ASSIGNMENT_TO_RIGHT) {
|
||||
$whichclass_expr = $conditional->left;
|
||||
@ -1525,10 +1446,6 @@ class AssertionFinder
|
||||
) {
|
||||
$if_types[$array_root . '[' . $first_var_name . ']'] = [[$prefix . 'array-key-exists']];
|
||||
}
|
||||
} elseif (self::hasNonEmptyCountCheck($expr)) {
|
||||
if ($first_var_name) {
|
||||
$if_types[$first_var_name] = [[$prefix . 'non-empty-countable']];
|
||||
}
|
||||
} else {
|
||||
$if_types = self::processCustomAssertion($expr, $this_class_name, $source, $negate);
|
||||
}
|
||||
@ -1789,52 +1706,6 @@ class AssertionFinder
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpParser\Node\Expr\BinaryOp $conditional
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
protected static function hasNonEmptyCountEqualityCheck(PhpParser\Node\Expr\BinaryOp $conditional)
|
||||
{
|
||||
$left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall
|
||||
&& $conditional->left->name instanceof PhpParser\Node\Name
|
||||
&& strtolower($conditional->left->name->parts[0]) === 'count';
|
||||
|
||||
$right_number = $conditional->right instanceof PhpParser\Node\Scalar\LNumber
|
||||
&& $conditional->right->value >= (
|
||||
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 0 : 1);
|
||||
|
||||
$operator_greater_than_or_equal =
|
||||
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
||||
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
||||
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
|
||||
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual;
|
||||
|
||||
if ($left_count && $right_number && $operator_greater_than_or_equal) {
|
||||
return self::ASSIGNMENT_TO_RIGHT;
|
||||
}
|
||||
|
||||
$right_count = $conditional->right instanceof PhpParser\Node\Expr\FuncCall
|
||||
&& $conditional->right->name instanceof PhpParser\Node\Name
|
||||
&& strtolower($conditional->right->name->parts[0]) === 'count';
|
||||
|
||||
$left_number = $conditional->left instanceof PhpParser\Node\Scalar\LNumber
|
||||
&& $conditional->left->value >= (
|
||||
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 0 : 1);
|
||||
|
||||
$operator_less_than_or_equal =
|
||||
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
||||
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
||||
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
|
||||
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual;
|
||||
|
||||
if ($right_count && $left_number && $operator_less_than_or_equal) {
|
||||
return self::ASSIGNMENT_TO_LEFT;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpParser\Node\Expr\BinaryOp $conditional
|
||||
*
|
||||
@ -2101,22 +1972,6 @@ class AssertionFinder
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpParser\Node\Expr\FuncCall $stmt
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function hasNonEmptyCountCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
||||
{
|
||||
if ($stmt->name instanceof PhpParser\Node\Name
|
||||
&& $stmt->name->parts === ['count']
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpParser\Node\Expr\FuncCall $stmt
|
||||
*
|
||||
|
@ -345,11 +345,8 @@ class ArrayAssignmentAnalyzer
|
||||
}
|
||||
|
||||
if (!$has_matching_objectlike_property) {
|
||||
$object_like = new ObjectLike([$key_value => $current_type]);
|
||||
$object_like->sealed = true;
|
||||
|
||||
$array_assignment_type = new Type\Union([
|
||||
$object_like,
|
||||
new ObjectLike([$key_value => $current_type]),
|
||||
]);
|
||||
|
||||
$new_child_type = Type::combineUnionTypes(
|
||||
@ -375,7 +372,7 @@ class ArrayAssignmentAnalyzer
|
||||
$array_atomic_key_type = Type::getInt();
|
||||
}
|
||||
|
||||
$array_atomic_type = new TNonEmptyArray([
|
||||
$array_atomic_type = new TArray([
|
||||
$array_atomic_key_type,
|
||||
$current_type,
|
||||
]);
|
||||
@ -386,17 +383,16 @@ class ArrayAssignmentAnalyzer
|
||||
$atomic_root_types = $root_type->getTypes();
|
||||
|
||||
if (isset($atomic_root_types['array'])) {
|
||||
if ($atomic_root_types['array'] instanceof TNonEmptyArray) {
|
||||
if ($atomic_root_types['array'] instanceof TNonEmptyArray
|
||||
&& $array_atomic_type instanceof TNonEmptyArray
|
||||
) {
|
||||
$array_atomic_type->count = $atomic_root_types['array']->count;
|
||||
} elseif ($atomic_root_types['array'] instanceof ObjectLike
|
||||
&& $array_atomic_type instanceof TNonEmptyArray
|
||||
&& $atomic_root_types['array']->sealed
|
||||
) {
|
||||
$array_atomic_type->count = count($atomic_root_types['array']->properties);
|
||||
$from_countable_object_like = true;
|
||||
} else {
|
||||
$array_atomic_type = new TNonEmptyArray(
|
||||
$array_atomic_type->type_params
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +84,26 @@ function array_diff(array $arr, array $arr2, array $arr3 = null, array $arr4 = n
|
||||
*/
|
||||
function array_diff_key(array $arr, array $arr2, array $arr3 = null, array $arr4 = null) {}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey
|
||||
* @psalm-template TValue
|
||||
*
|
||||
* @param array<TKey, TValue> $arr
|
||||
* @return null|TValue
|
||||
* @psalm-ignore-nullable-return
|
||||
*/
|
||||
function array_shift(array &$arr) {}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey
|
||||
* @psalm-template TValue
|
||||
*
|
||||
* @param array<TKey, TValue> $arr
|
||||
* @return null|TValue
|
||||
* @psalm-ignore-nullable-return
|
||||
*/
|
||||
function array_pop(array &$arr) {}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey
|
||||
* @psalm-template TValue
|
||||
@ -103,6 +123,56 @@ function array_reverse(array $arr, bool $preserve_keys = false) {}
|
||||
*/
|
||||
function array_flip(array $arr) {}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey
|
||||
* @psalm-template TValue
|
||||
*
|
||||
* @param array<TKey, TValue> $arr
|
||||
* @return TValue|false
|
||||
* @psalm-ignore-falsable-return
|
||||
*/
|
||||
function current(array $arr) {}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey
|
||||
* @psalm-template TValue
|
||||
*
|
||||
* @param array<TKey, TValue> $arr
|
||||
* @return TValue|false
|
||||
* @psalm-ignore-falsable-return
|
||||
*/
|
||||
function next(array &$arr) {}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey
|
||||
* @psalm-template TValue
|
||||
*
|
||||
* @param array<TKey, TValue> $arr
|
||||
* @return TValue|false
|
||||
* @psalm-ignore-falsable-return
|
||||
*/
|
||||
function prev(array &$arr) {}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey
|
||||
* @psalm-template TValue
|
||||
*
|
||||
* @param array<TKey, TValue> $arr
|
||||
* @return TValue|false
|
||||
* @psalm-ignore-falsable-return
|
||||
*/
|
||||
function reset(array &$arr) {}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey
|
||||
* @psalm-template TValue
|
||||
*
|
||||
* @param array<TKey, TValue> $arr
|
||||
* @return TValue|false
|
||||
* @psalm-ignore-falsable-return
|
||||
*/
|
||||
function end(array &$arr) {}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey
|
||||
*
|
||||
|
@ -49,9 +49,6 @@ class TypeCombination
|
||||
/** @var array<int, bool>|null */
|
||||
private $array_counts = [];
|
||||
|
||||
/** @var bool */
|
||||
private $array_sometimes_filled = false;
|
||||
|
||||
/** @var bool */
|
||||
private $array_always_filled = true;
|
||||
|
||||
@ -272,12 +269,7 @@ class TypeCombination
|
||||
);
|
||||
}
|
||||
|
||||
if ($combination->array_always_filled
|
||||
|| ($combination->array_sometimes_filled && $overwrite_empty_array)
|
||||
|| ($combination->objectlike_entries
|
||||
&& $combination->objectlike_sealed
|
||||
&& $overwrite_empty_array)
|
||||
) {
|
||||
if ($combination->array_always_filled) {
|
||||
if ($combination->array_counts && count($combination->array_counts) === 1) {
|
||||
$array_type = new TNonEmptyArray($generic_type_params);
|
||||
$array_type->count = array_keys($combination->array_counts)[0];
|
||||
@ -416,8 +408,6 @@ class TypeCombination
|
||||
$combination->array_counts[$type->count] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$combination->array_sometimes_filled = true;
|
||||
} else {
|
||||
$combination->array_always_filled = false;
|
||||
}
|
||||
|
@ -541,10 +541,6 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->classlike_storages) {
|
||||
throw new \LogicException('$this->classlike_storages should not be empty');
|
||||
}
|
||||
|
||||
$classlike_storage = array_pop($this->classlike_storages);
|
||||
|
||||
if ($classlike_storage->has_visitor_issues) {
|
||||
@ -579,10 +575,6 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
) {
|
||||
$this->function_template_types = [];
|
||||
} elseif ($node instanceof PhpParser\Node\FunctionLike) {
|
||||
if (!$this->functionlike_storages) {
|
||||
throw new \UnexpectedValueException('There should be function storages');
|
||||
}
|
||||
|
||||
$functionlike_storage = array_pop($this->functionlike_storages);
|
||||
|
||||
if ($functionlike_storage->has_visitor_issues) {
|
||||
|
@ -281,10 +281,9 @@ abstract class Type
|
||||
}
|
||||
}
|
||||
|
||||
/** @var TNamedObject|TGenericParam */
|
||||
/** @var array<int, TNamedObject|TGenericParam> $intersection_types */
|
||||
$first_type = array_shift($intersection_types);
|
||||
|
||||
/** @var array<int, TNamedObject|TGenericParam> $intersection_types */
|
||||
$first_type->extra_types = $intersection_types;
|
||||
|
||||
return $first_type;
|
||||
|
@ -22,7 +22,6 @@ use Psalm\Type\Atomic\THtmlEscapedString;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNonEmptyArray;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TNumeric;
|
||||
use Psalm\Type\Atomic\TNumericString;
|
||||
@ -89,9 +88,6 @@ abstract class Atomic
|
||||
case 'array':
|
||||
return new TArray([new Union([new TMixed]), new Union([new TMixed])]);
|
||||
|
||||
case 'non-empty-array':
|
||||
return new TNonEmptyArray([new Union([new TMixed]), new Union([new TMixed])]);
|
||||
|
||||
case 'resource':
|
||||
return $php_compatible ? new TNamedObject($value) : new TResource();
|
||||
|
||||
|
@ -82,10 +82,6 @@ class Reconciler
|
||||
|
||||
$key_parts = Reconciler::breakUpPathIntoParts($nk);
|
||||
|
||||
if (!$key_parts) {
|
||||
throw new \UnexpectedValueException('There should be some key parts');
|
||||
}
|
||||
|
||||
$base_key = array_shift($key_parts);
|
||||
|
||||
if (!isset($new_types[$base_key])) {
|
||||
@ -107,7 +103,6 @@ class Reconciler
|
||||
|
||||
if (strpos($array_key, '\'') !== false) {
|
||||
$new_types[$base_key][] = ['!string'];
|
||||
$new_types[$base_key][] = ['!~falsy'];
|
||||
}
|
||||
|
||||
$base_key = $new_base_key;
|
||||
@ -324,7 +319,7 @@ class Reconciler
|
||||
return Type::getMixed($inside_loop);
|
||||
}
|
||||
|
||||
if ($new_var_type === 'array-key-exists' || $new_var_type === 'non-empty-countable') {
|
||||
if ($new_var_type === 'array-key-exists') {
|
||||
return Type::getMixed();
|
||||
}
|
||||
|
||||
@ -746,28 +741,6 @@ class Reconciler
|
||||
return Type::getFloat();
|
||||
}
|
||||
|
||||
if ($new_var_type === 'non-empty-countable') {
|
||||
if ($existing_var_type->hasType('array')) {
|
||||
$array_atomic_type = $existing_var_type->getTypes()['array'];
|
||||
|
||||
if ($array_atomic_type instanceof Type\Atomic\TArray
|
||||
&& !$array_atomic_type instanceof Type\Atomic\TNonEmptyArray
|
||||
) {
|
||||
if ($array_atomic_type->getId() === 'array<empty, empty>') {
|
||||
$existing_var_type->removeType('array');
|
||||
} else {
|
||||
$existing_var_type->addType(
|
||||
new Type\Atomic\TNonEmptyArray(
|
||||
$array_atomic_type->type_params
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
}
|
||||
|
||||
if (substr($new_var_type, 0, 4) === 'isa-') {
|
||||
if ($existing_var_type->hasMixed()) {
|
||||
return Type::getMixed();
|
||||
@ -1424,31 +1397,6 @@ class Reconciler
|
||||
return $existing_var_type;
|
||||
}
|
||||
|
||||
if ($new_var_type === 'non-empty-countable') {
|
||||
if (isset($existing_var_atomic_types['array'])) {
|
||||
$array_atomic_type = $existing_var_atomic_types['array'];
|
||||
|
||||
if ($array_atomic_type instanceof Type\Atomic\TNonEmptyArray
|
||||
|| ($array_atomic_type instanceof Type\Atomic\ObjectLike && $array_atomic_type->sealed)
|
||||
) {
|
||||
$did_remove_type = true;
|
||||
|
||||
$existing_var_type->removeType('array');
|
||||
} elseif ($array_atomic_type->getId() !== 'array<empty, empty>') {
|
||||
$did_remove_type = true;
|
||||
|
||||
$existing_var_type->addType(new TArray(
|
||||
[
|
||||
new Type\Union([new TEmpty]),
|
||||
new Type\Union([new TEmpty]),
|
||||
]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
}
|
||||
|
||||
if ($new_var_type === 'false' && isset($existing_var_atomic_types['bool'])) {
|
||||
$existing_var_type->removeType('bool');
|
||||
$existing_var_type->addType(new TTrue);
|
||||
|
@ -42,7 +42,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
|
||||
$out[] = 4;',
|
||||
'assertions' => [
|
||||
'$out' => 'non-empty-array<int, int>',
|
||||
'$out' => 'array<int, int>',
|
||||
],
|
||||
],
|
||||
'genericArrayCreationWithInt' => [
|
||||
@ -53,7 +53,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$out[] = 4;
|
||||
}',
|
||||
'assertions' => [
|
||||
'$out' => 'non-empty-array<int, int>',
|
||||
'$out' => 'array<int, int>',
|
||||
],
|
||||
],
|
||||
'generic2dArrayCreation' => [
|
||||
@ -64,7 +64,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$out[] = [4];
|
||||
}',
|
||||
'assertions' => [
|
||||
'$out' => 'non-empty-array<int, array{0:int}>',
|
||||
'$out' => 'array<int, array{0:int}>',
|
||||
],
|
||||
],
|
||||
'generic2dArrayCreationAddedInIf' => [
|
||||
@ -82,9 +82,11 @@ class ArrayAssignmentTest extends TestCase
|
||||
$bits[] = 4;
|
||||
}
|
||||
|
||||
$out[] = $bits;',
|
||||
if ($bits) {
|
||||
$out[] = $bits;
|
||||
}',
|
||||
'assertions' => [
|
||||
'$out' => 'non-empty-array<int, non-empty-array<int, int>>',
|
||||
'$out' => 'array<int, array<int, int>>',
|
||||
],
|
||||
],
|
||||
'genericArrayCreationWithObjectAddedInIf' => [
|
||||
@ -158,7 +160,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$foo = [];
|
||||
$foo[] = "hello";',
|
||||
'assertions' => [
|
||||
'$foo' => 'non-empty-array<int, string>',
|
||||
'$foo' => 'array<int, string>',
|
||||
],
|
||||
],
|
||||
'implicit2dIntArrayCreation' => [
|
||||
@ -166,7 +168,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$foo = [];
|
||||
$foo[][] = "hello";',
|
||||
'assertions' => [
|
||||
'$foo' => 'non-empty-array<int, array<int, string>>',
|
||||
'$foo' => 'array<int, array<int, string>>',
|
||||
],
|
||||
],
|
||||
'implicit3dIntArrayCreation' => [
|
||||
@ -174,7 +176,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$foo = [];
|
||||
$foo[][][] = "hello";',
|
||||
'assertions' => [
|
||||
'$foo' => 'non-empty-array<int, array<int, array<int, string>>>',
|
||||
'$foo' => 'array<int, array<int, array<int, string>>>',
|
||||
],
|
||||
],
|
||||
'implicit4dIntArrayCreation' => [
|
||||
@ -182,7 +184,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$foo = [];
|
||||
$foo[][][][] = "hello";',
|
||||
'assertions' => [
|
||||
'$foo' => 'non-empty-array<int, array<int, array<int, array<int, string>>>>',
|
||||
'$foo' => 'array<int, array<int, array<int, array<int, string>>>>',
|
||||
],
|
||||
],
|
||||
'implicitIndexedIntArrayCreation' => [
|
||||
@ -368,8 +370,8 @@ class ArrayAssignmentTest extends TestCase
|
||||
$c = [];
|
||||
$c[$b][$b][] = "bam";',
|
||||
'assertions' => [
|
||||
'$a' => 'non-empty-array<string, array<int, string>>',
|
||||
'$c' => 'non-empty-array<string, array<string, array<int, string>>>',
|
||||
'$a' => 'array<string, array<int, string>>',
|
||||
'$c' => 'array<string, array<string, array<int, string>>>',
|
||||
],
|
||||
],
|
||||
'assignExplicitValueToGeneric' => [
|
||||
@ -378,7 +380,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$a = [];
|
||||
$a["foo"] = ["bar" => "baz"];',
|
||||
'assertions' => [
|
||||
'$a' => 'non-empty-array<string, non-empty-array<string, string>>',
|
||||
'$a' => 'array<string, array<string, string>>',
|
||||
],
|
||||
],
|
||||
'additionWithEmpty' => [
|
||||
@ -456,63 +458,41 @@ class ArrayAssignmentTest extends TestCase
|
||||
'$foo' => 'array{root:array{a:int, b:array{0:int, 1:int}}}',
|
||||
],
|
||||
],
|
||||
'updateStringIntKey1' => [
|
||||
'updateStringIntKey' => [
|
||||
'<?php
|
||||
$string = "c";
|
||||
$int = 5;
|
||||
|
||||
$a = [];
|
||||
|
||||
$a["a"] = 5;
|
||||
$a[0] = 3;',
|
||||
'assertions' => [
|
||||
'$a' => 'array{a:int, 0:int}',
|
||||
],
|
||||
],
|
||||
'updateStringIntKey2' => [
|
||||
'<?php
|
||||
$string = "c";
|
||||
$a[0] = 3;
|
||||
|
||||
$b = [];
|
||||
|
||||
$b[$string] = 5;
|
||||
$b[0] = 3;',
|
||||
'assertions' => [
|
||||
'$b' => 'non-empty-array<string|int, int>',
|
||||
],
|
||||
],
|
||||
'updateStringIntKey3' => [
|
||||
'<?php
|
||||
$string = "c";
|
||||
$b[0] = 3;
|
||||
|
||||
$c = [];
|
||||
|
||||
$c[0] = 3;
|
||||
$c[$string] = 5;',
|
||||
'assertions' => [
|
||||
'$c' => 'non-empty-array<int|string, int>',
|
||||
],
|
||||
],
|
||||
'updateStringIntKey4' => [
|
||||
'<?php
|
||||
$int = 5;
|
||||
$c[$string] = 5;
|
||||
|
||||
$d = [];
|
||||
|
||||
$d[$int] = 3;
|
||||
$d["a"] = 5;',
|
||||
'assertions' => [
|
||||
'$d' => 'non-empty-array<int|string, int>',
|
||||
],
|
||||
],
|
||||
'updateStringIntKey5' => [
|
||||
'<?php
|
||||
$string = "c";
|
||||
$int = 5;
|
||||
$d["a"] = 5;
|
||||
|
||||
$e = [];
|
||||
|
||||
$e[$int] = 3;
|
||||
$e[$string] = 5;',
|
||||
'assertions' => [
|
||||
'$e' => 'non-empty-array<string|int, int>',
|
||||
'$a' => 'array{a:int, 0:int}',
|
||||
'$b' => 'array<string|int, int>',
|
||||
'$c' => 'array<int|string, int>',
|
||||
'$d' => 'array<int|string, int>',
|
||||
'$e' => 'array<string|int, int>',
|
||||
],
|
||||
],
|
||||
'updateStringIntKeyWithIntRootAndNumberOffset' => [
|
||||
|
@ -138,7 +138,7 @@ class EmptyTest extends TestCase
|
||||
'<?php
|
||||
function testarray(array $data): void {
|
||||
foreach ($data as $item) {
|
||||
if (!empty($item["a"]) && !empty($item["b"]["c"])) {
|
||||
if (!empty($item["a"]) && !empty($item["b"]) && !empty($item["b"]["c"])) {
|
||||
echo "Found\n";
|
||||
}
|
||||
}
|
||||
|
@ -66,10 +66,6 @@ class ErrorAfterUpdateTest extends \Psalm\Tests\TestCase
|
||||
$config->setCustomErrorLevel($error_type, $error_level);
|
||||
}
|
||||
|
||||
if (!$file_stages) {
|
||||
throw new \UnexpectedValueException('$file_stages should not be empty');
|
||||
}
|
||||
|
||||
$end_files = array_pop($file_stages);
|
||||
|
||||
foreach ($file_stages as $files) {
|
||||
|
@ -68,10 +68,6 @@ class TemporaryUpdateTest extends \Psalm\Tests\TestCase
|
||||
$config->setCustomErrorLevel($error_type, $error_level);
|
||||
}
|
||||
|
||||
if (!$file_stages) {
|
||||
throw new \UnexpectedValueException('$file_stages should not be empty');
|
||||
}
|
||||
|
||||
$start_files = array_shift($file_stages);
|
||||
|
||||
// first batch
|
||||
|
@ -233,186 +233,6 @@ class FunctionCallTest extends TestCase
|
||||
],
|
||||
'error_levels' => ['MixedAssignment', 'MixedArgument'],
|
||||
],
|
||||
'arrayPopNonEmpty' => [
|
||||
'<?php
|
||||
/** @var array<string, int> */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$b = 5;
|
||||
if ($a) {
|
||||
$b = array_pop($a);
|
||||
}
|
||||
$c = array_pop($a);',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
'$c' => 'int|null',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterIsset' => [
|
||||
'<?php
|
||||
/** @var array<string, int> */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$b = 5;
|
||||
if (isset($a["a"])) {
|
||||
$b = array_pop($a);
|
||||
}',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterCount' => [
|
||||
'<?php
|
||||
/** @var array<string, int> */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$b = 5;
|
||||
if (count($a)) {
|
||||
$b = array_pop($a);
|
||||
}',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterCountEqualsOne' => [
|
||||
'<?php
|
||||
/** @var array<string, int> */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$b = 5;
|
||||
if (count($a) === 1) {
|
||||
$b = array_pop($a);
|
||||
}',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterCountSoftEqualsOne' => [
|
||||
'<?php
|
||||
/** @var array<string, int> */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$b = 5;
|
||||
if (count($a) == 1) {
|
||||
$b = array_pop($a);
|
||||
}',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterCountGreaterThanOne' => [
|
||||
'<?php
|
||||
/** @var array<string, int> */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$b = 5;
|
||||
if (count($a) > 0) {
|
||||
$b = array_pop($a);
|
||||
}',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterCountGreaterOrEqualsOne' => [
|
||||
'<?php
|
||||
/** @var array<string, int> */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$b = 5;
|
||||
if (count($a) >= 1) {
|
||||
$b = array_pop($a);
|
||||
}',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterCountEqualsOneReversed' => [
|
||||
'<?php
|
||||
/** @var array<string, int> */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$b = 5;
|
||||
if (1 === count($a)) {
|
||||
$b = array_pop($a);
|
||||
}',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterCountSoftEqualsOneReversed' => [
|
||||
'<?php
|
||||
/** @var array<string, int> */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$b = 5;
|
||||
if (1 == count($a)) {
|
||||
$b = array_pop($a);
|
||||
}',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterCountGreaterThanOneReversed' => [
|
||||
'<?php
|
||||
/** @var array<string, int> */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$b = 5;
|
||||
if (0 < count($a)) {
|
||||
$b = array_pop($a);
|
||||
}',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterCountGreatorOrEqualToOneReversed' => [
|
||||
'<?php
|
||||
/** @var array<string, int> */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$b = 5;
|
||||
if (1 <= count($a)) {
|
||||
$b = array_pop($a);
|
||||
}',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterThreeAssertions' => [
|
||||
'<?php
|
||||
class A {}
|
||||
class B extends A {
|
||||
/** @var array<int, string> */
|
||||
public $arr = [];
|
||||
}
|
||||
|
||||
/** @var array<A> */
|
||||
$replacement_stmts = [];
|
||||
|
||||
if (!$replacement_stmts
|
||||
|| !$replacement_stmts[0] instanceof B
|
||||
|| count($replacement_stmts[0]->arr) > 1
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$b = $replacement_stmts[0]->arr;',
|
||||
'assertions' => [
|
||||
'$b' => 'array<int, string>',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterArrayAddition' => [
|
||||
'<?php
|
||||
/** @var array<string, int> */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$a["foo"] = 10;
|
||||
$b = array_pop($a);',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'arrayPopNonEmptyAfterMixedArrayAddition' => [
|
||||
'<?php
|
||||
/** @var array */
|
||||
$a = ["a" => 5, "b" => 6, "c" => 7];
|
||||
$a[] = "hello";
|
||||
$b = array_pop($a);',
|
||||
'assertions' => [
|
||||
'$b' => 'string|mixed',
|
||||
],
|
||||
'error_levels' => [
|
||||
'MixedAssignment',
|
||||
],
|
||||
],
|
||||
'uasort' => [
|
||||
'<?php
|
||||
uasort(
|
||||
@ -585,7 +405,7 @@ class FunctionCallTest extends TestCase
|
||||
'$b' => 'string|false',
|
||||
],
|
||||
],
|
||||
'arrayPopNotNullable' => [
|
||||
'arrayPopIgnoreNullable' => [
|
||||
'<?php
|
||||
function expectsInt(int $a) : void {}
|
||||
|
||||
@ -832,7 +652,7 @@ class FunctionCallTest extends TestCase
|
||||
$arr = ["one", "two", "three"];
|
||||
$n = next($arr);',
|
||||
'assertions' => [
|
||||
'$n' => 'string|false',
|
||||
'$n' => 'false|string',
|
||||
],
|
||||
],
|
||||
'iteratorToArray' => [
|
||||
@ -1552,22 +1372,6 @@ class FunctionCallTest extends TestCase
|
||||
);',
|
||||
'error_message' => 'InvalidArgument',
|
||||
],
|
||||
'arrayPopNotNull' => [
|
||||
'<?php
|
||||
function expectsInt(int $a) : void {}
|
||||
|
||||
/**
|
||||
* @param array<mixed, array{item:int}> $list
|
||||
*/
|
||||
function test(array $list) : void
|
||||
{
|
||||
while (!empty($list)) {
|
||||
$tmp = array_pop($list);
|
||||
if ($tmp === null) {}
|
||||
}
|
||||
}',
|
||||
'error_message' => 'DocblockTypeContradiction',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ class IssetTest extends TestCase
|
||||
'<?php
|
||||
function testarray(array $data): void {
|
||||
foreach ($data as $item) {
|
||||
if (isset($item["a"]) && isset($item["b"]["c"])) {
|
||||
if (isset($item["a"]) && isset($item["b"]) && isset($item["b"]["c"])) {
|
||||
echo "Found\n";
|
||||
}
|
||||
}
|
||||
|
@ -409,6 +409,18 @@ class TypeAlgebraTest extends TestCase
|
||||
],
|
||||
'noParadoxAfterArrayAppending' => [
|
||||
'<?php
|
||||
/** @return array|false */
|
||||
function array_append2(array $errors) {
|
||||
if ($errors) {
|
||||
return $errors;
|
||||
}
|
||||
$errors[] = "deterministic";
|
||||
if ($errors) {
|
||||
return false;
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/** @return array|false */
|
||||
function array_append(array $errors) {
|
||||
if ($errors) {
|
||||
|
Loading…
Reference in New Issue
Block a user