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

feat(types): add properties-of<T> type #7359

Merged
merged 9 commits into from Feb 28, 2022
5 changes: 3 additions & 2 deletions docs/annotating_code/type_syntax/atomic_types.md
Expand Up @@ -49,9 +49,10 @@ Atomic types are the basic building block of all type information used in Psalm.
## Magical types

- [(T is true ? string : bool)](conditional_types.md)
- `key-of<Foo\Bar::ARRAY_CONST>`
- `value-of<Foo\Bar::ARRAY_CONST>`
- [`key-of<T>`](utility_types.md#key-oft)
- [`value-of<T>`](utility_types.md#value-oft)
- `T[K]`
- [`properties-of<T>`](utility_types.md#properties-oft)

## Top types, bottom types

Expand Down
137 changes: 137 additions & 0 deletions docs/annotating_code/type_syntax/utility_types.md
@@ -0,0 +1,137 @@
# Utility types
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are key-of and value-of documented? Maybe they should be added here? Maybe SomeClass::CONST_* as well? Probably others?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add these as well in another PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added key-of and value-of, SomeClass::CONST_* also could need some documentation, but I think it's not a utility type if you look at typescripts definition (https://www.typescriptlang.org/docs/handbook/utility-types.html)


Psalm supports some _magical_ utility types that brings superpower to the PHP type system.

## `key-of<T>`

(Psalm 5.0+)

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'`
- `key-of<string[]>` evaluates to `array-key`
- `key-of<T>` evaluates to the template param's offset-type (ensure `@template T of array`)

### 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)`

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

```php
/**
* @template T of array
* @param T $array
* @return list<key-of<T>>
*/
function getKeys($array) {
return array_keys($array);
}
```


## `value-of<T>`

(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`
- `value-of<string[]>` evaluates to `string`
- `value-of<T>` evaluates to the template param's value-type (ensure `@template T of array`)

### 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
/**
* @template T of array
* @param T $array
* @return value-of<T>[]
*/
function getValues($array) {
return array_values($array);
}
```

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


## `properties-of<T>`

(Psalm 5.0+)

This collection of _utility types_ construct a keyed-array type, with the names of non-static properties of a class as
keys, and their respective types as values. This can be useful if you need to convert objects into arrays.

```php
class A {
public string $foo = 'foo!';
public int $bar = 42;

/**
* @return properties-of<self>
*/
public function asArray(): array {
return [
'foo' => $this->foo,
'bar' => $this->bar,
];
}

/**
* @return list<key-of<properties-of<self>>>
*/
public function attributeNames(): array {
return ['foo', 'bar']
}
}
```

### Variants

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


### Limited template support

As there is no way to statically analyze if a method returns all properties of a generic param (e.g. via Reflection or
serialization), you have to annotate it where you assume it.

```php
/**
* @param T $object
* @return properties-of<T>
*/
public function asArray($object): array {
/** @var properties-of<T> */
$array = json_decode(json_encode($object), true);
return $array;
}


class A {
public string $foo = 'foo!';
public int $bar = 42;
}

$a = new A();
$aAsArray = asArray($a);
$aAsArray['foo']; // valid
$aAsArray['adams']; // error!
```
4 changes: 4 additions & 0 deletions docs/running_psalm/plugins/plugins_type_system.md
Expand Up @@ -59,6 +59,10 @@ The classes are as follows:

`TTemplateValueOf` - Represents the type used when using TValueOfArray when the type of the array is a template

`TPropertiesOf` - Represents properties and their types of a class as a keyed array (e.g. `properties-of<MyClass>`)

`TTemplatePropertiesOf` - Represents the type used when using TPropertiesOf when type of the class is a template

`TTypeAlias` - To be documented

### Scalar supertype
Expand Down