From ea89a6ba299c98f1c0792b185bb086a819937d5a Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 19 Dec 2018 08:45:14 -0500 Subject: [PATCH] Revert "First pass" This reverts commit 9ed047234f3d9c33c561ce3386bb38d43a507972. --- config.xsd | 2 - docs/configuration.md | 6 +- src/Psalm/Config.php | 20 -- .../Internal/Analyzer/CommentAnalyzer.php | 4 - .../Internal/Analyzer/FunctionAnalyzer.php | 163 +------------- .../Statements/Expression/AssertionFinder.php | 149 +------------ .../Assignment/ArrayAssignmentAnalyzer.php | 16 +- .../Internal/Stubs/CoreGenericFunctions.php | 70 ++++++ src/Psalm/Internal/Type/TypeCombination.php | 12 +- .../Internal/Visitor/ReflectorVisitor.php | 8 - src/Psalm/Type.php | 3 +- src/Psalm/Type/Atomic.php | 4 - src/Psalm/Type/Reconciler.php | 54 +---- tests/ArrayAssignmentTest.php | 74 +++---- tests/EmptyTest.php | 2 +- tests/FileUpdates/ErrorAfterUpdateTest.php | 4 - tests/FileUpdates/TemporaryUpdateTest.php | 4 - tests/FunctionCallTest.php | 200 +----------------- tests/IssetTest.php | 2 +- tests/TypeAlgebraTest.php | 12 ++ 20 files changed, 134 insertions(+), 675 deletions(-) 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 ' [ '