diff --git a/config.xsd b/config.xsd
index 2f3c6f059..c5c6595c9 100644
--- a/config.xsd
+++ b/config.xsd
@@ -37,8 +37,6 @@
-
-
diff --git a/docs/configuration.md b/docs/configuration.md
index 1604ef62e..be023d8d4 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -36,7 +36,7 @@ Psalm uses an XML config file. A barebones example looks like this:
- `allowCoercionFromStringToClassConst=[bool]`
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]`
- 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]`
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]`
@@ -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]`
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]`
- 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]`
- 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
diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php
index ff423055e..1be20e2e5 100644
--- a/src/Psalm/Config.php
+++ b/src/Psalm/Config.php
@@ -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
*/
@@ -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;
diff --git a/src/Psalm/Internal/Analyzer/CommentAnalyzer.php b/src/Psalm/Internal/Analyzer/CommentAnalyzer.php
index 058604aeb..51c8cb16d 100644
--- a/src/Psalm/Internal/Analyzer/CommentAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/CommentAnalyzer.php
@@ -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])) {
diff --git a/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php
index a211abd91..4b057bff5 100644
--- a/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php
@@ -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 $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 $call_args
*
@@ -922,59 +839,6 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
return new Type\Union([$first_arg_array->getGenericArrayType()]);
}
- /**
- * @param array $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 $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,
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php
index 9ee64852a..abbc1a403 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php
@@ -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
*
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php
index a9ec1c030..01e8311eb 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php
@@ -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
- );
}
}
}
diff --git a/src/Psalm/Internal/Stubs/CoreGenericFunctions.php b/src/Psalm/Internal/Stubs/CoreGenericFunctions.php
index e97b70aa6..e17c5934b 100644
--- a/src/Psalm/Internal/Stubs/CoreGenericFunctions.php
+++ b/src/Psalm/Internal/Stubs/CoreGenericFunctions.php
@@ -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 $arr
+ * @return null|TValue
+ * @psalm-ignore-nullable-return
+ */
+function array_shift(array &$arr) {}
+
+/**
+ * @psalm-template TKey
+ * @psalm-template TValue
+ *
+ * @param array $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 $arr
+ * @return TValue|false
+ * @psalm-ignore-falsable-return
+ */
+function current(array $arr) {}
+
+/**
+ * @psalm-template TKey
+ * @psalm-template TValue
+ *
+ * @param array $arr
+ * @return TValue|false
+ * @psalm-ignore-falsable-return
+ */
+function next(array &$arr) {}
+
+/**
+ * @psalm-template TKey
+ * @psalm-template TValue
+ *
+ * @param array $arr
+ * @return TValue|false
+ * @psalm-ignore-falsable-return
+ */
+function prev(array &$arr) {}
+
+/**
+ * @psalm-template TKey
+ * @psalm-template TValue
+ *
+ * @param array $arr
+ * @return TValue|false
+ * @psalm-ignore-falsable-return
+ */
+function reset(array &$arr) {}
+
+/**
+ * @psalm-template TKey
+ * @psalm-template TValue
+ *
+ * @param array $arr
+ * @return TValue|false
+ * @psalm-ignore-falsable-return
+ */
+function end(array &$arr) {}
+
/**
* @psalm-template TKey
*
diff --git a/src/Psalm/Internal/Type/TypeCombination.php b/src/Psalm/Internal/Type/TypeCombination.php
index 735ccc90d..45482646a 100644
--- a/src/Psalm/Internal/Type/TypeCombination.php
+++ b/src/Psalm/Internal/Type/TypeCombination.php
@@ -49,9 +49,6 @@ class TypeCombination
/** @var array|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;
}
diff --git a/src/Psalm/Internal/Visitor/ReflectorVisitor.php b/src/Psalm/Internal/Visitor/ReflectorVisitor.php
index 892c1d9af..16d425ba9 100644
--- a/src/Psalm/Internal/Visitor/ReflectorVisitor.php
+++ b/src/Psalm/Internal/Visitor/ReflectorVisitor.php
@@ -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) {
diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php
index 6df65874e..b1b476699 100644
--- a/src/Psalm/Type.php
+++ b/src/Psalm/Type.php
@@ -281,10 +281,9 @@ abstract class Type
}
}
- /** @var TNamedObject|TGenericParam */
+ /** @var array $intersection_types */
$first_type = array_shift($intersection_types);
- /** @var array $intersection_types */
$first_type->extra_types = $intersection_types;
return $first_type;
diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php
index a061e9039..de14743ac 100644
--- a/src/Psalm/Type/Atomic.php
+++ b/src/Psalm/Type/Atomic.php
@@ -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();
diff --git a/src/Psalm/Type/Reconciler.php b/src/Psalm/Type/Reconciler.php
index 6ee5999e5..14e33ae50 100644
--- a/src/Psalm/Type/Reconciler.php
+++ b/src/Psalm/Type/Reconciler.php
@@ -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') {
- $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') {
- $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);
diff --git a/tests/ArrayAssignmentTest.php b/tests/ArrayAssignmentTest.php
index 0c7f2dd10..f9e31623b 100644
--- a/tests/ArrayAssignmentTest.php
+++ b/tests/ArrayAssignmentTest.php
@@ -42,7 +42,7 @@ class ArrayAssignmentTest extends TestCase
$out[] = 4;',
'assertions' => [
- '$out' => 'non-empty-array',
+ '$out' => 'array',
],
],
'genericArrayCreationWithInt' => [
@@ -53,7 +53,7 @@ class ArrayAssignmentTest extends TestCase
$out[] = 4;
}',
'assertions' => [
- '$out' => 'non-empty-array',
+ '$out' => 'array',
],
],
'generic2dArrayCreation' => [
@@ -64,7 +64,7 @@ class ArrayAssignmentTest extends TestCase
$out[] = [4];
}',
'assertions' => [
- '$out' => 'non-empty-array',
+ '$out' => 'array',
],
],
'generic2dArrayCreationAddedInIf' => [
@@ -82,9 +82,11 @@ class ArrayAssignmentTest extends TestCase
$bits[] = 4;
}
- $out[] = $bits;',
+ if ($bits) {
+ $out[] = $bits;
+ }',
'assertions' => [
- '$out' => 'non-empty-array>',
+ '$out' => 'array>',
],
],
'genericArrayCreationWithObjectAddedInIf' => [
@@ -158,7 +160,7 @@ class ArrayAssignmentTest extends TestCase
$foo = [];
$foo[] = "hello";',
'assertions' => [
- '$foo' => 'non-empty-array',
+ '$foo' => 'array',
],
],
'implicit2dIntArrayCreation' => [
@@ -166,7 +168,7 @@ class ArrayAssignmentTest extends TestCase
$foo = [];
$foo[][] = "hello";',
'assertions' => [
- '$foo' => 'non-empty-array>',
+ '$foo' => 'array>',
],
],
'implicit3dIntArrayCreation' => [
@@ -174,7 +176,7 @@ class ArrayAssignmentTest extends TestCase
$foo = [];
$foo[][][] = "hello";',
'assertions' => [
- '$foo' => 'non-empty-array>>',
+ '$foo' => 'array>>',
],
],
'implicit4dIntArrayCreation' => [
@@ -182,7 +184,7 @@ class ArrayAssignmentTest extends TestCase
$foo = [];
$foo[][][][] = "hello";',
'assertions' => [
- '$foo' => 'non-empty-array>>>',
+ '$foo' => 'array>>>',
],
],
'implicitIndexedIntArrayCreation' => [
@@ -368,8 +370,8 @@ class ArrayAssignmentTest extends TestCase
$c = [];
$c[$b][$b][] = "bam";',
'assertions' => [
- '$a' => 'non-empty-array>',
- '$c' => 'non-empty-array>>',
+ '$a' => 'array>',
+ '$c' => 'array>>',
],
],
'assignExplicitValueToGeneric' => [
@@ -378,7 +380,7 @@ class ArrayAssignmentTest extends TestCase
$a = [];
$a["foo"] = ["bar" => "baz"];',
'assertions' => [
- '$a' => 'non-empty-array>',
+ '$a' => 'array>',
],
],
'additionWithEmpty' => [
@@ -456,63 +458,41 @@ class ArrayAssignmentTest extends TestCase
'$foo' => 'array{root:array{a:int, b:array{0:int, 1:int}}}',
],
],
- 'updateStringIntKey1' => [
+ 'updateStringIntKey' => [
' [
- '$a' => 'array{a:int, 0:int}',
- ],
- ],
- 'updateStringIntKey2' => [
- ' [
- '$b' => 'non-empty-array',
- ],
- ],
- 'updateStringIntKey3' => [
- ' [
- '$c' => 'non-empty-array',
- ],
- ],
- 'updateStringIntKey4' => [
- ' [
- '$d' => 'non-empty-array',
- ],
- ],
- 'updateStringIntKey5' => [
- ' [
- '$e' => 'non-empty-array',
+ '$a' => 'array{a:int, 0:int}',
+ '$b' => 'array',
+ '$c' => 'array',
+ '$d' => 'array',
+ '$e' => 'array',
],
],
'updateStringIntKeyWithIntRootAndNumberOffset' => [
diff --git a/tests/EmptyTest.php b/tests/EmptyTest.php
index 521145815..722c1d830 100644
--- a/tests/EmptyTest.php
+++ b/tests/EmptyTest.php
@@ -138,7 +138,7 @@ class EmptyTest extends TestCase
'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) {
diff --git a/tests/FileUpdates/TemporaryUpdateTest.php b/tests/FileUpdates/TemporaryUpdateTest.php
index a4e88a9e8..0879201cd 100644
--- a/tests/FileUpdates/TemporaryUpdateTest.php
+++ b/tests/FileUpdates/TemporaryUpdateTest.php
@@ -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
diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php
index 9259216e2..dfb7fddde 100644
--- a/tests/FunctionCallTest.php
+++ b/tests/FunctionCallTest.php
@@ -233,186 +233,6 @@ class FunctionCallTest extends TestCase
],
'error_levels' => ['MixedAssignment', 'MixedArgument'],
],
- 'arrayPopNonEmpty' => [
- ' */
- $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' => [
- ' */
- $a = ["a" => 5, "b" => 6, "c" => 7];
- $b = 5;
- if (isset($a["a"])) {
- $b = array_pop($a);
- }',
- 'assertions' => [
- '$b' => 'int',
- ],
- ],
- 'arrayPopNonEmptyAfterCount' => [
- ' */
- $a = ["a" => 5, "b" => 6, "c" => 7];
- $b = 5;
- if (count($a)) {
- $b = array_pop($a);
- }',
- 'assertions' => [
- '$b' => 'int',
- ],
- ],
- 'arrayPopNonEmptyAfterCountEqualsOne' => [
- ' */
- $a = ["a" => 5, "b" => 6, "c" => 7];
- $b = 5;
- if (count($a) === 1) {
- $b = array_pop($a);
- }',
- 'assertions' => [
- '$b' => 'int',
- ],
- ],
- 'arrayPopNonEmptyAfterCountSoftEqualsOne' => [
- ' */
- $a = ["a" => 5, "b" => 6, "c" => 7];
- $b = 5;
- if (count($a) == 1) {
- $b = array_pop($a);
- }',
- 'assertions' => [
- '$b' => 'int',
- ],
- ],
- 'arrayPopNonEmptyAfterCountGreaterThanOne' => [
- ' */
- $a = ["a" => 5, "b" => 6, "c" => 7];
- $b = 5;
- if (count($a) > 0) {
- $b = array_pop($a);
- }',
- 'assertions' => [
- '$b' => 'int',
- ],
- ],
- 'arrayPopNonEmptyAfterCountGreaterOrEqualsOne' => [
- ' */
- $a = ["a" => 5, "b" => 6, "c" => 7];
- $b = 5;
- if (count($a) >= 1) {
- $b = array_pop($a);
- }',
- 'assertions' => [
- '$b' => 'int',
- ],
- ],
- 'arrayPopNonEmptyAfterCountEqualsOneReversed' => [
- ' */
- $a = ["a" => 5, "b" => 6, "c" => 7];
- $b = 5;
- if (1 === count($a)) {
- $b = array_pop($a);
- }',
- 'assertions' => [
- '$b' => 'int',
- ],
- ],
- 'arrayPopNonEmptyAfterCountSoftEqualsOneReversed' => [
- ' */
- $a = ["a" => 5, "b" => 6, "c" => 7];
- $b = 5;
- if (1 == count($a)) {
- $b = array_pop($a);
- }',
- 'assertions' => [
- '$b' => 'int',
- ],
- ],
- 'arrayPopNonEmptyAfterCountGreaterThanOneReversed' => [
- ' */
- $a = ["a" => 5, "b" => 6, "c" => 7];
- $b = 5;
- if (0 < count($a)) {
- $b = array_pop($a);
- }',
- 'assertions' => [
- '$b' => 'int',
- ],
- ],
- 'arrayPopNonEmptyAfterCountGreatorOrEqualToOneReversed' => [
- ' */
- $a = ["a" => 5, "b" => 6, "c" => 7];
- $b = 5;
- if (1 <= count($a)) {
- $b = array_pop($a);
- }',
- 'assertions' => [
- '$b' => 'int',
- ],
- ],
- 'arrayPopNonEmptyAfterThreeAssertions' => [
- ' */
- public $arr = [];
- }
-
- /** @var array */
- $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',
- ],
- ],
- 'arrayPopNonEmptyAfterArrayAddition' => [
- ' */
- $a = ["a" => 5, "b" => 6, "c" => 7];
- $a["foo"] = 10;
- $b = array_pop($a);',
- 'assertions' => [
- '$b' => 'int',
- ],
- ],
- 'arrayPopNonEmptyAfterMixedArrayAddition' => [
- ' 5, "b" => 6, "c" => 7];
- $a[] = "hello";
- $b = array_pop($a);',
- 'assertions' => [
- '$b' => 'string|mixed',
- ],
- 'error_levels' => [
- 'MixedAssignment',
- ],
- ],
'uasort' => [
' 'string|false',
],
],
- 'arrayPopNotNullable' => [
+ 'arrayPopIgnoreNullable' => [
' [
- '$n' => 'string|false',
+ '$n' => 'false|string',
],
],
'iteratorToArray' => [
@@ -1552,22 +1372,6 @@ class FunctionCallTest extends TestCase
);',
'error_message' => 'InvalidArgument',
],
- 'arrayPopNotNull' => [
- ' $list
- */
- function test(array $list) : void
- {
- while (!empty($list)) {
- $tmp = array_pop($list);
- if ($tmp === null) {}
- }
- }',
- 'error_message' => 'DocblockTypeContradiction',
- ],
];
}
}
diff --git a/tests/IssetTest.php b/tests/IssetTest.php
index a6f52a1f9..7813a7f09 100644
--- a/tests/IssetTest.php
+++ b/tests/IssetTest.php
@@ -104,7 +104,7 @@ class IssetTest extends TestCase
' [
'