Generics in
from <A> to <Z>
Jiří Pudil
from Brno, CZE
open-sourcerer
coffee addict
guitar noisemaker
What are generics<?>
🤓 / 🤯 / 🤔
The identity function
🍎 → 🍎
🍏 → 🍏
🍊 → 🍊
The identity function
function identity(mixed $value): mixed {
return $value;
}
The identity function
function identity(mixed $value): mixed {
return $value;
}��PHPStan\dumpType(identity(42)); // mixed
PHPStan\dumpType(identity("string")); // mixed
PHPStan\dumpType(identity(true)); // mixed
PHPStan\dumpType(identity([])); // mixed
PHPStan\dumpType(identity(new stdClass)); // mixed
🍎 → ❓
🍏 → ❓
🍊 → ❓
The identity function
$result = identity(42);
$half = intdiv($result, 2);��
ERROR
Parameter #1 $num1 of function intdiv expects int, mixed given.
The identity function
function identity🍎() { /* … */ }
function identity🍏() { /* … */ }
function identity🍊() { /* … */ }
The identity function
�����function identity(mixed $value): mixed {
return $value;
}
The identity function
/**� * @template Value� * @param Value $value� * @return Value� */�function identity(mixed $value): mixed {
return $value;
}
The identity function
/**� * @template Value� * @param Value $value� * @return Value� */�function identity(mixed $value): mixed {
return $value;
}
The identity function
PHPStan\dumpType(identity(42)); // int
PHPStan\dumpType(identity("string")); // string
PHPStan\dumpType(identity(true)); // bool
PHPStan\dumpType(identity([])); // array{}
PHPStan\dumpType(identity(new stdClass)); // stdClass
The identity function
/**� * @template Value� * @param Value $value� * @return Value� */�function identity(mixed $value): mixed {
return $value;
}��PHPStan\dumpType(identity(42));
The identity function
/**� * @template Value� * @param Value $value� * @return Value� */�function identity(mixed $value): mixed {
return $value;
}��PHPStan\dumpType(identity(42));
The identity function
/**� * @template Value� * @param Value $value� * @return Value� */�function identity(mixed $value): mixed {
return $value;
}��PHPStan\dumpType(identity(42));
The identity function
/**� * @template Value = 42� * @param Value $value� * @return Value� */�function identity(mixed $value): mixed {
return $value;
}��PHPStan\dumpType(identity(42));
The identity function
/**� * @template Value = 42� * @param 42 $value� * @return 42� */�function identity(mixed $value): mixed {
return $value;
}��PHPStan\dumpType(identity(42));
You have already used them.
You have already used them.
/**� * @param Token[] @tokens� */�function assemble(array $tokens): string {� // ...�}
You have already used them.
/**� * @param Token[] @tokens� */�function assemble(array $tokens): string {� // ...�}
You have already used them.
/**� * @param Token[] @tokens� */�function assemble(array $tokens): string {� PHPStan\dumpType($tokens); // array<Token>�}
You have already used them.
/**� * @template Value� */�type array {� /** @return Value */� public function offsetGet(): mixed;�}
You have already used them.
/**� * @template Value� */�type array {� /** @return Value */� public function offsetGet(): mixed;�}
array<Token>
You have already used them.
/**� * @template Value� */�type array {� /** @return Value */� public function offsetGet(): mixed;�}
array<Token>
You have already used them.
/**� * @template Value = Token� */�type array {� /** @return Value */� public function offsetGet(): mixed;�}
array<Token>
You have already used them.
/**� * @template Value = Token� */�type array {� /** @return Token */� public function offsetGet(): mixed;�}��array<Token>
You have already used them.
/**� * @param Token[] @tokens� */�function assemble(array $tokens): string {� $token = $tokens[0];� PHPStan\dumpType($token); // Token�}
A generic class
A generic class
/**
* @template T
*/
final class Reference
{
/**
* @param T $value
*/
public function __construct(
public readonly mixed $value,
) {}
}
A generic class
$ref = new Reference(42);
PHPStan\dumpType($ref); // Reference<int>
PHPStan\dumpType($ref->value); // int
A generic class
$ref = new Reference("hello");
PHPStan\dumpType($ref); // Reference<string>
PHPStan\dumpType($ref->value); // string
A generic class
/**
* @template T
*/
final class Queue
{
/** @var T[] */
private mixed $items = [];
/** @return T|null */
public function dequeue(): mixed
{
return array_shift($this->items);
}
/** @param T $item */
public function enqueue(mixed $item): void
{
$this->items[] = $item;
}
}
A generic class
/** @var Queue<int> $queue */
$queue = new Queue();
$first = $queue->dequeue();
PHPStan\dumpType($first); // int|null
🤓 / 🤯 / 🤔
Once generic, always generic
Once generic, always generic
/**
* @template T
*/
interface Comparator
{
/**
* @param T $a
* @param T $b
*/
public function compare(mixed $a, mixed $b): int;
}
Once generic, always generic
abstract class AbstractComparator implements Comparator�{�}
ERROR
Class AbstractComparator implements generic interface Comparator but does not specify its types: T
Once generic, always generic
/**� * @template T� * @implements Comparator<T>� */�abstract class AbstractComparator implements Comparator�{�}
Once generic, always generic
/**� * @template T� * @implements Comparator<T>� */�abstract class AbstractComparator implements Comparator�{�}
Once generic, always generic
/**
* @implements Comparator<HasWeight>
*/
final class Scales implements Comparator
{
public function compare(mixed $a, mixed $b): int
{
return $a->weight() <=> $b->weight();
}
}
Type parameter bounds
Type parameter bounds
interface Consumer
{
public function consume(Message $message): void;
}
Type parameter bounds
final class ProcessOrderConsumer implements Consumer
{
public function consume(ProcessOrder $message): void;
}
Type parameter bounds
/**
* @template M
*/
interface Consumer
{� /** @param M $message */
public function consume(Message $message): void;
}
Type parameter bounds
/**� * @implements Consumer<ProcessOrder>� */�final class ProcessOrderConsumer implements Consumer
{
public function consume(Message $message): void;
}
Type parameter bounds
/**
* @template M
*/
interface Consumer
{� /** @param M $message */
public function consume(Message $message): void;
}��ERROR
PHPDoc tag @param for parameter $message with type M is not subtype of native type Message.
Type parameter bounds
/**
* @template M of Message
*/
interface Consumer
{� /** @param M $message */
public function consume(Message $message): void;
}
Type parameter bounds
/**� * @implements Consumer<stdClass>� */�final class ProcessOrderConsumer implements Consumer
{
public function consume(Message $message): void;
}
ERROR
Type stdClass in generic type Consumer<stdClass> in PHPDoc tag @implements is not subtype of template type M of Message of interface Consumer.
Generic class string
Generic class string
�����function resolve(string $type): object {� // ...�}��$scales = resolve(Scales::class);�PHPStan\dumpType($scales);
Generic class string
�����function resolve(string $type): object {� // ...�}��$scales = resolve(Scales::class);�PHPStan\dumpType($scales); // mixed
Generic class string
/**� * @template T� * @param class-string<T> $type� * @return T� */�function resolve(string $type): object {� // ...�}��$scales = resolve(Scales::class);�PHPStan\dumpType($scales); // Scales
🍺
Generic variance
Generic variance
/**
* @template T
*/
final class Queue
{
/** @var T[] */
private mixed $items = [];
/** @return T|null */
public function dequeue(): mixed
{
return array_shift($this->items);
}
/** @param T $item */
public function enqueue(mixed $item): void
{
$this->items[] = $item;
}
}
Generic variance
Animal
Queue<Animal>
Dog
Queue<Dog>
?
Generic variance
/**� * @param Queue<Animal> $queue� */�function printFirstFrom(Queue $queue): void {� $animal = $queue->dequeue();� var_dump($animal);�}
Generic variance
/** @var Queue<Dog> $queue */�$queue = new Queue();��printFirstFrom($queue);
Generic variance
/** @var Queue<Dog> $queue */�$queue = new Queue();��printFirstFrom($queue);
ERROR
Parameter #1 $queue of function printFirstFrom expects Queue<Animal>, Queue<Dog> given.
Generic variance
Animal
Queue<Animal>
Dog
Queue<Dog>
X
Generic variance
/**� * @param Queue<Animal> $queue� */�function helloKitty(Queue $queue): void {� $queue->enqueue(new Cat);�}
Generic variance
/** @var Queue<Dog> $queue */�$queue = new Queue();��helloKitty($queue);
Generic covariance
Generic covariance
Animal
Queue<Animal>
Dog
Queue<Dog>
Generic covariance
/**
* @template-covariant T
*/
final class Queue
{
// ...
}
Generic covariance
/** @var Queue<Dog> $queue */�$queue = new Queue();��printFirstFrom($queue);
Generic covariance
/** @var Queue<Dog> $queue */�$queue = new Queue();��helloKitty($queue);
Generic covariance
/**
* @template T
*/
final class Queue
{
/** @param T $item */
public function enqueue(mixed $item): void
{
$this->items[] = $item;
}
}
ERROR
Template type T is declared as covariant, but occurs in a contravariant position in parameter item of method Queue::enqueue().
Generic covariance
/**� * @param Queue<Animal> $queue� */�function printFirstFrom(Queue $queue): void {� $animal = $queue->dequeue();� var_dump($animal);�}
Generic covariance
/**� * @param Queue<Animal> $queue� */�function helloKitty(Queue $queue): void {� $queue->enqueue(new Cat);�}
Generic contravariance
Generic contravariance
Animal
Queue<Animal>
Dog
Queue<Dog>
Generic contravariance
HasWeight
Comparator<HasWeight>
Animal
Comparator<Animal>
Generic contravariance
/**
* @template T
*/
interface Comparator
{
/**
* @param T $a
* @param T $b
*/
public function compare(mixed $a, mixed $b): int;
}
Generic contravariance
/**
* @implements Comparator<HasWeight>
*/
final class Scales implements Comparator
{
public function compare(mixed $a, mixed $b): int
{
return $a->weight() <=> $b->weight();
}
}
Generic contravariance
/**� * @template T� */�final class Queue�{� /**� * @param Comparator<T> $comparator� */� public function sort(Comparator $comparator): void {}�}
Generic contravariance
/** @var Queue<Animal> $queue */
$queue = new Queue();�$queue->sort(new Scales);
ERROR
Parameter #1 $comparator of method Queue<Animal>::sort() expects Comparator<Animal>, Scales given.
Generic contravariance
/**
* @template-contravariant T
*/
interface Comparator
{
/**
* @param T $a
* @param T $b
*/
public function compare(mixed $a, mixed $b): int;
}
Generic contravariance
/** @var Queue<Animal> $queue */
$queue = new Queue();�$queue->sort(new Scales);
Generic contravariance
/**
* @template-contravariant T
*/
interface Comparator
{
/** @return T */
public function getReferenceValue(): mixed;
}
ERROR
Template type T is declared as contravariant, but occurs in covariant position in return type of method Comparator::getReferenceValue().
🤓 / 🤯 / 🤔
Type projections
Type projections
/**
* @template T
*/
final class Queue
{
// ...� public function dequeue(): mixed { /* ... */ }� public function enqueue(mixed $item): void { /* ... */ }
}
Type projections
/**� * @param Queue<covariant Animal> $queue� */�function printFirstFrom(Queue $queue): void {� $animal = $queue->dequeue();� var_dump($animal);�}
Type projections
/**� * @param Queue<covariant Animal> $queue� */�function helloKitty(Queue $queue): void {� $queue->enqueue(new Cat);�}
🤓 / 🤯 / 🤔
🍺
Any questions<?>