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

Misc fixes #9031

Merged
merged 3 commits into from
Dec 30, 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
4 changes: 3 additions & 1 deletion docs/annotating_code/type_syntax/atomic_types.md
Expand Up @@ -2,6 +2,8 @@

Atomic types are the basic building block of all type information used in Psalm. Multiple atomic types can be combined, either with [union types](union_types.md) or [intersection types](intersection_types.md). Psalm allows many different sorts of atomic types to be expressed in docblock syntax:

**Note**: you can view detailed documentation and usage examples for all atomic types by clicking on each type in the following list.

* [Scalar types](scalar_types.md)
* [bool](scalar_types.md#scalar)
* [int](scalar_types.md#scalar)
Expand All @@ -26,7 +28,7 @@ Atomic types are the basic building block of all type information used in Psalm.
* [Exception, Foo\MyClass and `Foo\MyClass<Bar>`](object_types.md#named-objectsmd)
* [Generator](object_types.md#generators)
* [Array types](array_types.md)
* [array&lt;int, string&gt; ](array_types.md#generic-arrays)
* [array&lt;int, string&gt;](array_types.md#generic-arrays)
* [non-empty-array](array_types.md#non-empty-array)
* [string\[\]](array_types.md#phpdoc-syntax)
* [list & non-empty-list](array_types.md#lists)
Expand Down
14 changes: 8 additions & 6 deletions docs/annotating_code/type_syntax/utility_types.md
Expand Up @@ -9,6 +9,7 @@ Psalm supports some _magical_ utility types that brings superpower to the PHP ty
The `key-of` utility returns the offset-type for any [array type](array_types.md).

Some examples:

- `key-of<Foo\Bar::ARRAY_CONST>` evaluates to offset-type of `ARRAY_CONST` (Psalm 3.3+)
- `key-of<list<mixed>>` evaluates to `int`
- `key-of<array{a: mixed, b: mixed}|array{c: mixed}>` evaluates to `'a'|'b'|'c'`
Expand All @@ -18,6 +19,7 @@ Some examples:
### Notes on template usage

If you use `key-of` with a template param, you can fulfill the type check only with these allowed methods:

- `array_keys($t)`
- `array_key_first($t)`
- `array_key_last($t)`
Expand All @@ -35,14 +37,14 @@ function getKeys($array) {
}
```


## `value-of<T>`
## value-of&lt;T&gt;

(Psalm 5.0+)

The `value-of` utility returns the value-type for any [array type](array_types.md).

Some examples:

- `value-of<Foo\Bar::ARRAY_CONST>` evaluates to value-type of `ARRAY_CONST` (Psalm 3.3+)
- `value-of<list<float>>` evaluates to `float`
- `value-of<array{a: bool, b: int}|array{c: string}>` evaluates to `bool|int|string`
Expand All @@ -52,6 +54,7 @@ Some examples:
### Notes on template usage

If you use `value-of` with a template param, you can fulfill the type check only with these allowed methods:

- `array_values`

```php
Expand All @@ -67,8 +70,7 @@ function getValues($array) {

Currently `in_array($value, $t)` **does not** infer that `$value` is of `value-of<T>`.


## `properties-of<T>`
## properties-of&lt;T&gt;

(Psalm 5.0+)

Expand Down Expand Up @@ -103,11 +105,11 @@ class A {

Note that `properties-of<T>` will return **all non-static** properties. There are the following subtypes to pick only
properties with a certain visibility:

- `public-properties-of<T>`
- `protected-properties-of<T>`
- `private-properties-of<T>`


### Sealed array support

Use final classes if you want to properties-of and get_object_vars to return sealed arrays:
Expand Down Expand Up @@ -139,7 +141,7 @@ $b = asArray(new B);
/** @psalm-trace $b */; // array{foo: string, bar: int, baz: float}
```

## `class-string-map<T as Foo, T>`
## class-string-map&lt;T as Foo, T&gt;

Used to indicate an array where each value is equal an instance of the class string contained in the key:

Expand Down
Expand Up @@ -614,7 +614,7 @@ private static function getGlobalTypeInner(string $var_id, bool $files_full_path
if ($var_id === '$http_response_header') {
// $http_response_header exists only in the local scope after a successful network request
return new Union([
Type::getNonEmptyListAtomic(Type::getNonFalsyString())
Type::getNonEmptyListAtomic(Type::getNonFalsyString()),
], [
'possibly_undefined' => true,
]);
Expand Down
8 changes: 4 additions & 4 deletions src/Psalm/Internal/Type/AssertionReconciler.php
Expand Up @@ -934,20 +934,20 @@ private static function handleLiteralEquality(
}

$can_be_equal = false;
$did_remove_type = false;
$redundant = true;

$existing_var_type = $existing_var_type->getBuilder();
foreach ($existing_var_atomic_types as $atomic_key => $atomic_type) {
if (get_class($atomic_type) === TNamedObject::class
&& $atomic_type->value === $fq_enum_name
) {
$can_be_equal = true;
$did_remove_type = true;
$redundant = false;
$existing_var_type->removeType($atomic_key);
$existing_var_type->addType(new TEnumCase($fq_enum_name, $case_name));
} elseif ($atomic_key !== $assertion_type->getKey()) {
$existing_var_type->removeType($atomic_key);
$did_remove_type = true;
$redundant = false;
} else {
$can_be_equal = true;
}
Expand All @@ -956,7 +956,7 @@ private static function handleLiteralEquality(

if ($var_id
&& $code_location
&& (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1))
&& (!$can_be_equal || ($redundant && count($existing_var_atomic_types) === 1))
) {
self::triggerIssueForImpossible(
$existing_var_type,
Expand Down
20 changes: 10 additions & 10 deletions src/Psalm/Internal/Type/NegatedAssertionReconciler.php
Expand Up @@ -316,7 +316,7 @@ private static function handleLiteralNegatedEquality(
$existing_var_type = $existing_var_type->getBuilder();
$existing_var_atomic_types = $existing_var_type->getAtomicTypes();

$did_remove_type = false;
$redundant = true;
$did_match_literal_type = false;

$scalar_var_type = null;
Expand All @@ -327,7 +327,7 @@ private static function handleLiteralNegatedEquality(
$did_match_literal_type = true;

if ($existing_var_type->removeType($assertion_type->getKey())) {
$did_remove_type = true;
$redundant = false;
}
}

Expand All @@ -336,7 +336,7 @@ private static function handleLiteralNegatedEquality(
if ($existing_range_types) {
foreach ($existing_range_types as $int_key => $literal_type) {
if ($literal_type->contains($assertion_type->value)) {
$did_remove_type = true;
$redundant = false;
$existing_var_type->removeType($int_key);
if ($literal_type->min_bound === null
|| $literal_type->min_bound <= $assertion_type->value - 1
Expand All @@ -361,7 +361,7 @@ private static function handleLiteralNegatedEquality(
if (isset($existing_var_type->getAtomicTypes()['int'])
&& get_class($existing_var_type->getAtomicTypes()['int']) === Type\Atomic\TInt::class
) {
$did_remove_type = true;
$redundant = false;
//this may be used to generate a range containing any int except the one that was asserted against
//but this is failing some tests
/*$existing_var_type->removeType('int');
Expand All @@ -377,7 +377,7 @@ private static function handleLiteralNegatedEquality(
$did_match_literal_type = true;

if ($existing_var_type->removeType($assertion_type->getKey())) {
$did_remove_type = true;
$redundant = false;
}
} elseif ($assertion_type->value === "") {
$existing_var_type->addType(new TNonEmptyString());
Expand All @@ -391,7 +391,7 @@ private static function handleLiteralNegatedEquality(
$did_match_literal_type = true;

if ($existing_var_type->removeType($assertion_type->getKey())) {
$did_remove_type = true;
$redundant = false;
}
}
} else {
Expand All @@ -413,7 +413,7 @@ private static function handleLiteralNegatedEquality(
$scalar_var_type = $assertion_type;
} else {
$existing_var_type->removeType($atomic_type->getKey());
$did_remove_type = true;
$redundant = false;

foreach ($enum_storage->enum_cases as $alt_case_name => $_) {
if ($alt_case_name === $case_name) {
Expand All @@ -430,7 +430,7 @@ private static function handleLiteralNegatedEquality(
$did_match_literal_type = true;
} elseif ($atomic_key === $assertion_type->getKey()) {
$existing_var_type->removeType($assertion_type->getKey());
$did_remove_type = true;
$redundant = false;
}
}
}
Expand All @@ -439,14 +439,14 @@ private static function handleLiteralNegatedEquality(

if ($key && $code_location) {
if ($did_match_literal_type
&& (!$did_remove_type || count($existing_var_atomic_types) === 1)
&& ($redundant || count($existing_var_atomic_types) === 1)
) {
self::triggerIssueForImpossible(
$existing_var_type,
$old_var_type_string,
$key,
$assertion,
!$did_remove_type,
$redundant,
$negated,
$code_location,
$suppressed_issues,
Expand Down