1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Revert "First pass"

This reverts commit 9ed047234f.
This commit is contained in:
Matthew Brown 2018-12-19 08:45:14 -05:00
parent 20f7147af1
commit ea89a6ba29
20 changed files with 134 additions and 675 deletions

View File

@ -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" />

View File

@ -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

View File

@ -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;

View File

@ -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])) {

View File

@ -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,13 +100,8 @@ 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 $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;
}
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;
}
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;
}
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;
}
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,

View File

@ -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
*

View File

@ -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
);
}
}
}

View File

@ -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
*

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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' => [

View File

@ -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";
}
}

View File

@ -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) {

View File

@ -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

View File

@ -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',
],
];
}
}

View File

@ -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";
}
}

View File

@ -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) {