Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removed TEmpty #6662

Merged
merged 7 commits into from Jan 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 7 additions & 6 deletions docs/annotating_code/type_syntax/atomic_types.md
Expand Up @@ -52,19 +52,20 @@ Atomic types are the basic building block of all type information used in Psalm.
- `value-of<Foo\Bar::ARRAY_CONST>`
- `T[K]`

## Top types, bottom types and empty
## Top types, bottom types

### `mixed`

This is the _top type_ in PHP's type system, and represents a lack of type information. Psalm warns about `mixed` types when the `totallyTyped` flag is turned on, or when you're on level 1.

### `never`
It can be aliased to `no-return` or `never-return` in docblocks. Note: it replaced the old `empty` type that used to exist in Psalm

This is the _bottom type_ in PHP's type system, and usually represents a return type for a function that can never actually return, such as `die()`, `exit()`, or a function that always throws an exception. It may also be written in docblocks as `no-return` or `never-return`.

### `empty`

A type that's equivalent to a "coming soon" sign. Psalm uses this type when it’s awaiting more information — a good example is the type of the empty array `[]`, which Psalm types as `array<empty, empty>`. Psalm treats `empty` in a somewhat similar fashion to `never` when combining types together — `empty|int` becomes `int`, just as `never|string` becomes `string`.
This is the _bottom type_ in PHP's type system. It's used to describe a type that has no possible value. It can happen in multiple cases:
- the actual `never` type from PHP 8.1 (can be used in docblocks for older versions). This type can be used as a return type for functions that will never return, either because they always throw exceptions or always exit()
- an union type that have been stripped for all its possible types. (For example, if a variable is `string|int` and we perform a is_bool() check in a condition, the type of the variable in the condition will be `never` as the condition will never be entered)
- it can represent a placeholder for types yet to come — a good example is the type of the empty array `[]`, which Psalm types as `array<never, never>`, the content of the array is void so it can accept any content
- it can also happen in the same context as the line above for templates that have yet to be defined

## Other

Expand Down
4 changes: 1 addition & 3 deletions docs/running_psalm/plugins/plugins_type_system.md
Expand Up @@ -27,16 +27,14 @@ The classes are as follows:

`TNull` - denotes the `null` type

`TNever` - denotes the `no-return`/`never-return` type for functions that never return, either throwing an exception or terminating (like the builtin `exit()`).
`TNever` - denotes the `no-return`/`never-return` type for functions that never return, either throwing an exception or terminating (like the builtin `exit()`). Also used for union types that can have no possible types (impossible intersections for example). Empty arrays `[]` have the type `array<never, never>`.

`TMixed` - denotes the `mixed` type, used when you don’t know the type of an expression.

`TNonEmptyMixed `- as above, but not empty. Generated for `$x` inside the `if` statement `if ($x) {...}` when `$x` is `mixed` outside.

`TEmptyMixed` - as above, but empty. Generated for `$x` inside the `if` statement `if (!$x) {...}` when `$x` is `mixed` outside.

`TEmpty` - denotes the `empty` type, used to describe a type corresponding to no value whatsoever. Empty arrays `[]` have the type `array<empty, empty>`.

`TIterable` - denotes the [`iterable` type](https://www.php.net/manual/en/language.types.iterable.php) (which can also result from an `is_iterable` check).

`TResource` - denotes the `resource` type (e.g. a file handle).
Expand Down
2 changes: 1 addition & 1 deletion src/Psalm/Internal/Analyzer/ClassAnalyzer.php
Expand Up @@ -1553,7 +1553,7 @@ private function checkForMissingPropertyType(

if ($suggested_type && !$suggested_type->isNull()) {
$message .= ' - consider ' . str_replace(
['<array-key, mixed>', '<empty, empty>'],
['<array-key, mixed>', '<never, never>'],
'',
(string)$suggested_type
);
Expand Down
2 changes: 1 addition & 1 deletion src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php
Expand Up @@ -50,7 +50,7 @@ abstract class ClassLikeAnalyzer extends SourceAnalyzer
'bool' => 'bool',
'false' => 'false',
'object' => 'object',
'empty' => 'empty',
'never' => 'never',
'callable' => 'callable',
'array' => 'array',
'iterable' => 'iterable',
Expand Down
Expand Up @@ -237,12 +237,12 @@ public static function verifyReturnType(
}

$number_of_types = count($inferred_return_type_parts);
// we filter TNever and TEmpty that have no bearing on the return type
// we filter TNever that have no bearing on the return type
if ($number_of_types > 1) {
$inferred_return_type_parts = array_filter(
$inferred_return_type_parts,
static function (Union $union_type): bool {
return !($union_type->isNever() || $union_type->isEmpty());
return !$union_type->isNever();
}
);
}
Expand Down Expand Up @@ -490,7 +490,7 @@ static function (Union $union_type): bool {
return null;
}

if ($inferred_return_type->hasMixed() || $inferred_return_type->isEmpty()) {
if ($inferred_return_type->hasMixed()) {
if (IssueBuffer::accepts(
new MixedInferredReturnType(
'Could not verify return type \'' . $declared_return_type . '\' for ' .
Expand Down
2 changes: 1 addition & 1 deletion src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php
Expand Up @@ -503,7 +503,7 @@ public function analyze(
&& !$inferred_return_type->isSingleIntLiteral()
&& !$inferred_return_type->isSingleStringLiteral()
&& !$inferred_return_type->isTrue()
&& $inferred_return_type->getId() !== 'array<empty, empty>'
&& $inferred_return_type->getId() !== 'array<never, never>'
) {
$manipulator->makePure();
}
Expand Down
10 changes: 6 additions & 4 deletions src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php
Expand Up @@ -49,6 +49,7 @@
use Psalm\Type\Atomic\TList;
use Psalm\Type\Atomic\TMixed;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TNever;
use Psalm\Type\Atomic\TNonEmptyArray;
use Psalm\Type\Atomic\TNonEmptyList;
use Psalm\Type\Atomic\TNull;
Expand Down Expand Up @@ -434,9 +435,7 @@ public static function checkIteratorType(
}

// if it's an empty array, we cannot iterate over it
if ($iterator_atomic_type instanceof TArray
&& $iterator_atomic_type->type_params[1]->isEmpty()
) {
if ($iterator_atomic_type instanceof TArray && $iterator_atomic_type->isEmptyArray()) {
$always_non_empty_array = false;
$has_valid_iterator = true;
continue;
Expand Down Expand Up @@ -505,7 +504,10 @@ public static function checkIteratorType(
$invalid_iterator_types[] = $iterator_atomic_type->getKey();

$value_type = Type::getMixed();
} elseif ($iterator_atomic_type instanceof TObject || $iterator_atomic_type instanceof TMixed) {
} elseif ($iterator_atomic_type instanceof TObject ||
$iterator_atomic_type instanceof TMixed ||
$iterator_atomic_type instanceof TNever
) {
$has_valid_iterator = true;
$value_type = Type::getMixed();
$key_type = Type::getMixed();
Expand Down
Expand Up @@ -171,7 +171,7 @@ public static function analyze(
);

if (isset($case_vars_in_scope_reconciled[$switch_var_id])
&& $case_vars_in_scope_reconciled[$switch_var_id]->isEmpty()
&& $case_vars_in_scope_reconciled[$switch_var_id]->isNever()
) {
$all_options_matched = true;
}
Expand Down
Expand Up @@ -591,7 +591,7 @@ private static function handleNonReturningCase(
if (!$case->cond
&& $switch_var_id
&& isset($case_context->vars_in_scope[$switch_var_id])
&& $case_context->vars_in_scope[$switch_var_id]->isEmpty()
&& $case_context->vars_in_scope[$switch_var_id]->isNever()
) {
if (IssueBuffer::accepts(
new ParadoxicalCondition(
Expand Down
Expand Up @@ -547,7 +547,7 @@ private static function handleUnpackedArray(
&& count($unpacked_atomic_type->type_params) === 2
)) {
/** @psalm-suppress PossiblyUndefinedArrayOffset provably true, but Psalm can’t see it */
if ($unpacked_atomic_type->type_params[1]->isEmpty()) {
if ($unpacked_atomic_type->type_params[1]->isNever()) {
continue;
}
$array_creation_info->can_create_objectlike = false;
Expand Down Expand Up @@ -579,7 +579,7 @@ private static function handleUnpackedArray(
)
);
} elseif ($unpacked_atomic_type instanceof TList) {
if ($unpacked_atomic_type->type_param->isEmpty()) {
if ($unpacked_atomic_type->type_param->isNever()) {
continue;
}
$array_creation_info->can_create_objectlike = false;
Expand Down
Expand Up @@ -739,7 +739,7 @@ private static function analyzeNestedArrayAssignment(
return;
}

if ($child_stmt_var_type->isEmpty()) {
if ($child_stmt_var_type->isNever()) {
$child_stmt_var_type = Type::getEmptyArray();
$statements_analyzer->node_data->setType($child_stmt->var, $child_stmt_var_type);
}
Expand Down
Expand Up @@ -569,7 +569,7 @@ public static function analyze(
return false;
}

$context->vars_in_scope[$var_id] = Type::getEmpty();
$context->vars_in_scope[$var_id] = Type::getNever();

$context->inside_assignment = $was_in_assignment;

Expand Down Expand Up @@ -1024,11 +1024,11 @@ public static function assignByRefParam(
$by_ref_out_type->parent_nodes += $existing_type->parent_nodes;
}

if ($existing_type->getId() !== 'array<empty, empty>') {
if ($existing_type->getId() !== 'array<never, never>') {
$context->vars_in_scope[$var_id] = $by_ref_out_type;

if (!($stmt_type = $statements_analyzer->node_data->getType($stmt))
|| $stmt_type->isEmpty()
|| $stmt_type->isNever()
) {
$statements_analyzer->node_data->setType($stmt, clone $by_ref_type);
}
Expand All @@ -1043,7 +1043,7 @@ public static function assignByRefParam(

$stmt_type = $statements_analyzer->node_data->getType($stmt);

if (!$stmt_type || $stmt_type->isEmpty()) {
if (!$stmt_type || $stmt_type->isNever()) {
$statements_analyzer->node_data->setType($stmt, clone $by_ref_type);
}

Expand Down
Expand Up @@ -75,9 +75,9 @@ public static function analyze(
$right_type = $nodes->getType($right);
$config = Config::getInstance();

if ($left_type && $left_type->isEmpty()) {
if ($left_type && $left_type->isNever()) {
$left_type = $right_type;
} elseif ($right_type && $right_type->isEmpty()) {
} elseif ($right_type && $right_type->isNever()) {
$right_type = $left_type;
}

Expand Down Expand Up @@ -896,7 +896,7 @@ public static function arithmeticOperation(
$result = $operand1 - $operand2;
} elseif ($operation instanceof PhpParser\Node\Expr\BinaryOp\Mod) {
if ($operand2 === 0) {
return Type::getEmpty();
return Type::getNever();
}

$result = $operand1 % $operand2;
Expand All @@ -916,7 +916,7 @@ public static function arithmeticOperation(
$result = $operand1 >> $operand2;
} elseif ($operation instanceof PhpParser\Node\Expr\BinaryOp\Div) {
if ($operand2 === 0) {
return Type::getEmpty();
return Type::getNever();
}

$result = $operand1 / $operand2;
Expand Down Expand Up @@ -1269,9 +1269,11 @@ private static function analyzePowBetweenIntRange(
$new_result_type = Type::getInt(true, 0);
} elseif ($right_type_part->min_bound === 0 && $right_type_part->max_bound === 0) {
$new_result_type = Type::getInt(true, 1);
} elseif ($right_type_part->isNegative()) {
$new_result_type = Type::getFloat();
} else {
//technically could be a float(INF)...
$new_result_type = Type::getEmpty();
$new_result_type = new Union([new TFloat(), new TLiteralInt(0), new TLiteralInt(1)]);
$new_result_type->from_calculation = true;
}
} else {
//$left_type_part may be a mix of positive, negative and 0
Expand Down Expand Up @@ -1305,7 +1307,7 @@ private static function analyzeModBetweenIntRange(
if ($right_type_part->min_bound !== null && $right_type_part->min_bound === $right_type_part->max_bound) {
//if the second operand is a literal, we can be pretty detailed
if ($right_type_part->max_bound === 0) {
$new_result_type = Type::getEmpty();
$new_result_type = Type::getNever();
} else {
if ($left_type_part->isPositiveOrZero()) {
if ($right_type_part->isPositive()) {
Expand Down
Expand Up @@ -1360,7 +1360,7 @@ private static function coerceValueAfterGatekeeperArgument(
&& $input_atomic_type->value === $param_atomic_type->value
) {
foreach ($input_atomic_type->type_params as $i => $type_param) {
if ($type_param->isEmpty() && isset($param_atomic_type->type_params[$i])) {
if ($type_param->isNever() && isset($param_atomic_type->type_params[$i])) {
$input_type_changed = true;

$input_atomic_type->type_params[$i] = clone $param_atomic_type->type_params[$i];
Expand Down
Expand Up @@ -1207,7 +1207,7 @@ private static function evaluateArbitraryParam(
);

foreach ($context->vars_in_scope[$var_id]->getAtomicTypes() as $type) {
if ($type instanceof TArray && $type->type_params[1]->isEmpty()) {
if ($type instanceof TArray && $type->isEmptyArray()) {
$context->vars_in_scope[$var_id]->removeType('array');
$context->vars_in_scope[$var_id]->addType(
new TArray(
Expand Down
Expand Up @@ -33,9 +33,9 @@
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TCallable;
use Psalm\Type\Atomic\TClosure;
use Psalm\Type\Atomic\TEmpty;
use Psalm\Type\Atomic\TKeyedArray;
use Psalm\Type\Atomic\TList;
use Psalm\Type\Atomic\TNever;
use Psalm\Type\Atomic\TNonEmptyArray;
use Psalm\Type\Atomic\TNonEmptyList;
use Psalm\Type\Union;
Expand Down Expand Up @@ -544,8 +544,8 @@ public static function handleByRefArrayAdjustment(
if ($array_atomic_type->count === 0) {
$array_atomic_type = new TArray(
[
new Union([new TEmpty]),
new Union([new TEmpty]),
new Union([new TNever]),
new Union([new TNever]),
]
);
} else {
Expand All @@ -561,8 +561,8 @@ public static function handleByRefArrayAdjustment(
if ($array_atomic_type->count === 0) {
$array_atomic_type = new TArray(
[
new Union([new TEmpty]),
new Union([new TEmpty]),
new Union([new TNever]),
new Union([new TNever]),
]
);
} else {
Expand Down
Expand Up @@ -144,7 +144,7 @@ public static function fetch(
$template_result->lower_bounds[$template_name] = [
'fn-' . $function_id => [
new TemplateBound(
Type::getEmpty()
Type::getNever()
)
]
];
Expand Down Expand Up @@ -390,14 +390,13 @@ private static function getReturnTypeFromCallMapWithArgs(
foreach ($atomic_types['array']->properties as $property) {
// empty, never and possibly undefined can't count for min value
if (!$property->possibly_undefined
&& !$property->isEmpty()
&& !$property->isNever()
) {
$min++;
}

//empty and never can't count for max value because we know keys are undefined
if (!$property->isEmpty() && !$property->isNever()) {
//never can't count for max value because we know keys are undefined
if (!$property->isNever()) {
$max++;
}
}
Expand All @@ -416,8 +415,7 @@ private static function getReturnTypeFromCallMapWithArgs(
}

if ($atomic_types['array'] instanceof TArray
&& $atomic_types['array']->type_params[0]->isEmpty()
&& $atomic_types['array']->type_params[1]->isEmpty()
&& $atomic_types['array']->isEmptyArray()
) {
return Type::getInt(false, 0);
}
Expand Down
Expand Up @@ -27,12 +27,12 @@
use Psalm\Storage\ClassLikeStorage;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TEmpty;
use Psalm\Type\Atomic\TEmptyMixed;
use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TGenericObject;
use Psalm\Type\Atomic\TMixed;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TNever;
use Psalm\Type\Atomic\TNonEmptyMixed;
use Psalm\Type\Atomic\TNull;
use Psalm\Type\Atomic\TObject;
Expand Down Expand Up @@ -571,7 +571,7 @@ private static function handleInvalidClass(

case TTemplateParam::class:
case TEmptyMixed::class:
case TEmpty::class:
case TNever::class:
case TMixed::class:
case TNonEmptyMixed::class:
case TObject::class:
Expand Down
Expand Up @@ -588,7 +588,7 @@ public static function replaceTemplateTypes(
} else {
$template_result->lower_bounds[$template_type->param_name] = [
($template_type->defining_class) => [
new TemplateBound(Type::getEmpty())
new TemplateBound(Type::getNever())
]
];
}
Expand Down
Expand Up @@ -516,7 +516,7 @@ function ($bounds) use ($codebase) {
);
} else {
if ($fq_class_name === 'SplObjectStorage') {
$generic_param_type = Type::getEmpty();
$generic_param_type = Type::getNever();
} else {
$generic_param_type = clone array_values($base_type)[0];
}
Expand Down