diff --git a/docs/configuration.md b/docs/configuration.md index 07cdd123b..096a8b9db 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -93,7 +93,7 @@ Allows you to specify whether or not to use the typed iterator docblock format s allowCoercionFromStringToClassConst="[bool]" > ``` -When `true`, strings can be coerced to [`class-string`](supported_annotations.md#param-class-stringt), with Psalm emitting a `TypeCoercion` issue. If disabled, that issue changes to a more serious one. Defaults to `true`. +When `true`, strings can be coerced to [`class-string`](templated_annotations.md#param-class-stringt), with Psalm emitting a `TypeCoercion` issue. If disabled, that issue changes to a more serious one. Defaults to `true`. #### `allowStringToStandInForClass` ```xml diff --git a/docs/index.md b/docs/index.md index 7243795a3..1b1d375fc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,6 +13,7 @@ - [Issue types](issues.md) - [Typing in Psalm](typing_in_psalm.md) - [Supported Annotations](supported_annotations.md) + - [Templated Annotations](templated_annotations.md) - [Plugins](plugins.md) - [Checking non-PHP files](checking_non_php_files.md) - [How Psalm works](how_psalm_works.md) diff --git a/docs/supported_annotations.md b/docs/supported_annotations.md index b0b253f7d..3fd4e5c34 100644 --- a/docs/supported_annotations.md +++ b/docs/supported_annotations.md @@ -235,224 +235,6 @@ $a = new A(); $a->bar = 5; // this call fails ``` -## Templating - -### `@template` - -The `@template` tag allows classes and functions to implement type parameter-like functionality found in many other languages. - -While `@template` tag order matters (i.e. for key-value pair extending), names don't matter outside the scope of the class or function in which they're declared. - -As a very simple example, this function returns whatever is passed in: - -```php -/** - * @template T - * @psalm-param T $t - * @return T - */ -function mirror($t) { - return $t; -} - -$a = 5; -$b = mirror(5); // Psalm knows the result is an int -``` - -Psalm also uses `@template` annotations in its stubbed versions of PHP array functions e.g. - -```php -/** - * Takes one array with keys and another with values and combines them - * - * @template TKey - * @template TValue - * - * @param array $arr - * @param array $arr2 - * @return array - */ -function array_combine(array $arr, array $arr2) {} -``` - -### `@param class-string` - -Psalm also allows you to parameterise class types - -```php -/** - * @template T - * @psalm-param class-string $class - * @return T - */ -function instantiator(string $class) { - return new $class(); -} - -class Foo {} - -$a = instantiator(Foo::class); // Psalm knows the result is an object of type Foo -``` - -### Template inheritance - -Psalm allows you to extend templated classes with `@extends`/`@template-extends`: - -```php -/** - * @template T - */ -class ParentClass {} - -/** - * @extends ParentClass - */ -class ChildClass extends ParentClass {} -``` - -similarly you can implement interfaces with `@implements`/`@template-implements` - -```php -/** - * @template T - */ -interface IFoo {} - -/** - * @implements IFoo - */ -class Foo implements IFoo {} -``` - -and import traits with `@use`/`@template-use` - -```php -/** - * @template T - */ -trait MyTrait {} - -class Foo { - /** - * @use MyTrait - */ - use MyTrait; -} -``` - -You can also extend one templated class with another, e.g. - -```php -/** - * @template T1 - */ -class ParentClass {} - -/** - * @template T2 - * @extends ParentClass - */ -class ChildClass extends ParentClass {} -``` - -### Template constraints - -You can use `@template of ` to restrict input. For example, to restrict to a given class you can use - -```php -class Foo {} -class FooChild extends Foo {} - -/** - * @template T of Foo - * @psalm-param T $class - * @return array - */ -function makeArray($t) { - return [$t]; -} -$a = makeArray(new Foo()); // typed as array -$b = makeArray(new FooChild()); // typed as array -$c = makeArray(new stdClass()); // type error -``` - -Templated types aren't limited to key-value pairs, and you can re-use templates across multiple arguments of a template-supporting type: -```php -/** - * @template T0 as array-key - * - * @template-implements IteratorAggregate - */ -abstract class Foo implements IteratorAggregate { - /** - * @var int - */ - protected $rand_min; - - /** - * @var int - */ - protected $rand_max; - - public function __construct(int $rand_min, int $rand_max) { - $this->rand_min = $rand_min; - $this->rand_max = $rand_max; - } - - /** - * @return Generator - */ - public function getIterator() : Generator { - $j = random_int($this->rand_min, $this->rand_max); - for($i = $this->rand_min; $i <= $j; $i += 1) { - yield $this->getFuzzyType($i) => $i ** $i; - } - - return $this->getFuzzyType($j); - } - - /** - * @return T0 - */ - abstract protected function getFuzzyType(int $i); -} - -/** - * @template-extends Foo - */ -class Bar extends Foo { - protected function getFuzzyType(int $i) : int { - return $i; - } -} - -/** - * @template-extends Foo - */ -class Baz extends Foo { - protected function getFuzzyType(int $i) : string { - return static::class . '[' . $i . ']'; - } -} -``` - -### Builtin templated classes and interfaces - -Psalm has support for a number of builtin classes and interfaces that you can extend/implement in your own code. - -- `interface Traversable` -- `interface ArrayAccess` -- `interface IteratorAggregate extends Traversable` -- `interface Iterator extends Traversable` -- `interface SeekableIterator extends Iterator` - -- `class Generator extends Traversable` -- `class ArrayObject implements IteratorAggregate, ArrayAccess` -- `class ArrayIterator implements SeekableIterator, ArrayAccess` -- `class DOMNodeList implements Traversable` -- `class SplDoublyLinkedList implements Iterator, ArrayAccess` -- `class SplQueue extends SplDoublyLinkedList` - ## Type Syntax Psalm supports PHPDoc’s [type syntax](https://docs.phpdoc.org/guides/types.html), and also the [proposed PHPDoc PSR type syntax](https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#appendix-a-types). diff --git a/docs/templated_annotations.md b/docs/templated_annotations.md new file mode 100644 index 000000000..0bddd5d3b --- /dev/null +++ b/docs/templated_annotations.md @@ -0,0 +1,217 @@ +## Templating + +### `@template` + +The `@template` tag allows classes and functions to implement type parameter-like functionality found in many other languages. + +While `@template` tag order matters (i.e. for key-value pair extending), names don't matter outside the scope of the class or function in which they're declared. + +As a very simple example, this function returns whatever is passed in: + +```php +/** + * @template T + * @psalm-param T $t + * @return T + */ +function mirror($t) { + return $t; +} + +$a = 5; +$b = mirror(5); // Psalm knows the result is an int +``` + +Psalm also uses `@template` annotations in its stubbed versions of PHP array functions e.g. + +```php +/** + * Takes one array with keys and another with values and combines them + * + * @template TKey + * @template TValue + * + * @param array $arr + * @param array $arr2 + * @return array + */ +function array_combine(array $arr, array $arr2) {} +``` + +### `@param class-string` + +Psalm also allows you to parameterise class types + +```php +/** + * @template T + * @psalm-param class-string $class + * @return T + */ +function instantiator(string $class) { + return new $class(); +} + +class Foo {} + +$a = instantiator(Foo::class); // Psalm knows the result is an object of type Foo +``` + +### Template inheritance + +Psalm allows you to extend templated classes with `@extends`/`@template-extends`: + +```php +/** + * @template T + */ +class ParentClass {} + +/** + * @extends ParentClass + */ +class ChildClass extends ParentClass {} +``` + +similarly you can implement interfaces with `@implements`/`@template-implements` + +```php +/** + * @template T + */ +interface IFoo {} + +/** + * @implements IFoo + */ +class Foo implements IFoo {} +``` + +and import traits with `@use`/`@template-use` + +```php +/** + * @template T + */ +trait MyTrait {} + +class Foo { + /** + * @use MyTrait + */ + use MyTrait; +} +``` + +You can also extend one templated class with another, e.g. + +```php +/** + * @template T1 + */ +class ParentClass {} + +/** + * @template T2 + * @extends ParentClass + */ +class ChildClass extends ParentClass {} +``` + +### Template constraints + +You can use `@template of ` to restrict input. For example, to restrict to a given class you can use + +```php +class Foo {} +class FooChild extends Foo {} + +/** + * @template T of Foo + * @psalm-param T $class + * @return array + */ +function makeArray($t) { + return [$t]; +} +$a = makeArray(new Foo()); // typed as array +$b = makeArray(new FooChild()); // typed as array +$c = makeArray(new stdClass()); // type error +``` + +Templated types aren't limited to key-value pairs, and you can re-use templates across multiple arguments of a template-supporting type: +```php +/** + * @template T0 as array-key + * + * @template-implements IteratorAggregate + */ +abstract class Foo implements IteratorAggregate { + /** + * @var int + */ + protected $rand_min; + + /** + * @var int + */ + protected $rand_max; + + public function __construct(int $rand_min, int $rand_max) { + $this->rand_min = $rand_min; + $this->rand_max = $rand_max; + } + + /** + * @return Generator + */ + public function getIterator() : Generator { + $j = random_int($this->rand_min, $this->rand_max); + for($i = $this->rand_min; $i <= $j; $i += 1) { + yield $this->getFuzzyType($i) => $i ** $i; + } + + return $this->getFuzzyType($j); + } + + /** + * @return T0 + */ + abstract protected function getFuzzyType(int $i); +} + +/** + * @template-extends Foo + */ +class Bar extends Foo { + protected function getFuzzyType(int $i) : int { + return $i; + } +} + +/** + * @template-extends Foo + */ +class Baz extends Foo { + protected function getFuzzyType(int $i) : string { + return static::class . '[' . $i . ']'; + } +} +``` + +### Builtin templated classes and interfaces + +Psalm has support for a number of builtin classes and interfaces that you can extend/implement in your own code. + +- `interface Traversable` +- `interface ArrayAccess` +- `interface IteratorAggregate extends Traversable` +- `interface Iterator extends Traversable` +- `interface SeekableIterator extends Iterator` + +- `class Generator extends Traversable` +- `class ArrayObject implements IteratorAggregate, ArrayAccess` +- `class ArrayIterator implements SeekableIterator, ArrayAccess` +- `class DOMNodeList implements Traversable` +- `class SplDoublyLinkedList implements Iterator, ArrayAccess` +- `class SplQueue extends SplDoublyLinkedList`