,error_levels?:string[]}> */ public function providerValidCodeParse(): iterable { return [ 'phanTuple' => [ '_0 = $_0; } /** * @return int * The arity of this tuple */ public function arity(): int { return (int)static::ARITY; } /** * @return array * An array of all elements in this tuple. */ public function toArray(): array { return [ $this->_0, ]; } } /** * A tuple of 2 elements. * * @template T0 * The type of element zero * * @template T1 * The type of element one * * @extends Tuple1 */ class Tuple2 extends Tuple1 { /** @var int */ const ARITY = 2; /** @var T1 */ public $_1; /** * @param T0 $_0 * The 0th element * * @param T1 $_1 * The 1st element */ public function __construct($_0, $_1) { parent::__construct($_0); $this->_1 = $_1; } /** * @return array * An array of all elements in this tuple. */ public function toArray(): array { return [ $this->_0, $this->_1, ]; } } $a = new Tuple2("cool", 5); /** @return void */ function takes_int(int $i) {} /** @return void */ function takes_string(string $s) {} takes_string($a->_0); takes_int($a->_1);', ], 'templateExtendsSameName' => [ 'v = $v; } /** * @return TValue */ public function getValue() { return $this->v; } } /** * @template TKey * @template TValue * @template-extends ValueContainer */ class KeyValueContainer extends ValueContainer { /** * @var TKey */ private $k; /** * @param TKey $k * @param TValue $v */ public function __construct($k, $v) { $this->k = $k; parent::__construct($v); } /** * @return TKey */ public function getKey() { return $this->k; } } $a = new KeyValueContainer("hello", 15); $b = $a->getValue();', [ '$a' => 'KeyValueContainer', '$b' => 'int', ], ], 'templateExtendsDifferentName' => [ 'v = $v; } /** * @return TValue */ public function getValue() { return $this->v; } } /** * @template TKey * @template Tv * @template-extends ValueContainer */ class KeyValueContainer extends ValueContainer { /** * @var TKey */ private $k; /** * @param TKey $k * @param Tv $v */ public function __construct($k, $v) { $this->k = $k; parent::__construct($v); } /** * @return TKey */ public function getKey() { return $this->k; } } $a = new KeyValueContainer("hello", 15); $b = $a->getValue();', [ '$a' => 'KeyValueContainer', '$b' => 'int', ], ], 'extendsWithNonTemplate' => [ ' */ class FooContainer extends Container { /** * @return Foo */ public function getItem() { return new Foo(); } } /** * @template TItem * @param Container $c * @return TItem */ function getItemFromContainer(Container $c) { return $c->getItem(); } $fc = new FooContainer(); $f1 = $fc->getItem(); $f2 = getItemFromContainer($fc);', [ '$fc' => 'FooContainer', '$f1' => 'Foo', '$f2' => 'Foo', ], ], 'supportBareExtends' => [ ' */ class FooContainer extends Container { /** * @return Foo */ public function getItem() { return new Foo(); } } /** * @template TItem * @param Container $c * @return TItem */ function getItemFromContainer(Container $c) { return $c->getItem(); } $fc = new FooContainer(); $f1 = $fc->getItem(); $f2 = getItemFromContainer($fc);', [ '$fc' => 'FooContainer', '$f1' => 'Foo', '$f2' => 'Foo', ], ], 'allowExtendingParameterisedTypeParam' => [ ' */ class FooContainer extends Container { /** @param Foo $obj */ public function uri($obj) : string { return "hello"; } }', ], 'extendsWithNonTemplateWithoutImplementing' => [ 'id = $id; } /** * @return T */ public function getID() { return $this->id; } } /** * @template-extends User */ class AppUser extends User {} $au = new AppUser(-1); $id = $au->getId();', [ '$au' => 'AppUser', '$id' => 'int', ], ], 'extendsTwiceSameNameCorrect' => [ 'v = $v; } /** * @return T */ public function getValue() { return $this->v; } } /** * @template T * @template-extends Container */ class ChildContainer extends Container {} /** * @template T * @template-extends ChildContainer */ class GrandChildContainer extends ChildContainer {} $fc = new GrandChildContainer(5); $a = $fc->getValue();', [ '$a' => 'int', ], ], 'extendsTwiceDifferentNameUnbrokenChain' => [ 'v = $v; } /** * @return T1 */ public function getValue() { return $this->v; } } /** * @psalm-template T2 * @extends Container */ class ChildContainer extends Container {} /** * @psalm-template T3 * @extends ChildContainer */ class GrandChildContainer extends ChildContainer {} $fc = new GrandChildContainer(5); $a = $fc->getValue();', [ '$a' => 'int', ], ], 'templateExtendsOnceAndBound' => [ ' */ class AnotherRepo extends Repo {} $a = new AnotherRepo(); $b = $a->findOne();', [ '$a' => 'AnotherRepo', '$b' => 'SpecificEntity|null', ], ], 'templateExtendsTwiceAndBound' => [ ' */ class CommonAppRepo extends Repo {} class SpecificEntity {} /** @template-extends CommonAppRepo */ class SpecificRepo extends CommonAppRepo {} $a = new SpecificRepo(); $b = $a->findOne();', [ '$a' => 'SpecificRepo', '$b' => 'SpecificEntity|null', ], ], 'multipleArgConstraints' => [ ' [ ' */ class SomeIterator implements IteratorAggregate { public function getIterator() { yield new Foo; } } $i = (new SomeIterator())->getIterator();', [ '$i' => 'Traversable', ], ], 'templateCountOnExtendedAndImplemented' => [ ' */ class Repository implements Selectable {} interface SomeEntity {} /** * @template-extends Repository */ class SomeRepository extends Repository {}', ], 'iterateOverExtendedArrayObjectWithParam' => [ ' */ class Collection extends ArrayObject { /** @var class-string */ public $class; /** @param class-string $class */ public function __construct(string $class) { $this->class = $class; } } /** @return Collection */ function getFooCollection() : Collection { return new Collection(Foo::class); } foreach (getFooCollection() as $i => $foo) { $foo->bar(); }', ], 'constructExtendedArrayIteratorWithTemplateExtends' => [ ' */ class Collection1 extends ArrayIterator{} class Collection2 extends Collection1{} class Collection3 extends Collection2{} foreach ((new Collection1(["a" => "b"])) as $a) {} /** @psalm-suppress MixedAssignment */ foreach ((new Collection2(["a" => "b"])) as $a) {} /** @psalm-suppress MixedAssignment */ foreach ((new Collection3(["a" => "b"])) as $a) {} foreach ((new Collection1([])) as $i) {} /** @psalm-suppress MixedAssignment */ foreach ((new Collection2([])) as $i) {} /** @psalm-suppress MixedAssignment */ foreach ((new Collection3([])) as $i) {}', ], 'iterateOverExtendedArrayObjectWithoutParam' => [ ' */ class Collection extends ArrayObject { /** @var class-string */ public $class; /** @param class-string $class */ public function __construct(string $class) { $this->class = $class; } } function getFooCollection() : Collection { return new Collection(Foo::class); } foreach (getFooCollection() as $i => $foo) {}', ], 'iterateOverExtendedArrayObjectFromClassCall' => [ ' */ public static function getSelfCollection() : Collection { return new Collection(self::class); } } /** * @template T as O * @template-extends ArrayObject */ class Collection extends ArrayObject { /** @var class-string */ public $class; /** @param class-string $class */ public function __construct(string $class) { $this->class = $class; } } foreach (Foo::getSelfCollection() as $i => $foo) { $foo->bar(); }', ], 'iterateOverExtendedArrayObjectInsideClass' => [ ' $c */ public static function takesSelfCollection(Collection $c) : void { foreach ($c as $i => $foo) { $foo->bar(); } } } /** * @template T as O * @template-extends ArrayObject */ class Collection extends ArrayObject { /** @var class-string */ public $class; /** @param class-string $class */ public function __construct(string $class) { $this->class = $class; } }', ], 'iterateOverExtendedArrayObjectThisClassIteration' => [ ' */ class Collection extends ArrayObject { /** @var class-string */ public $class; /** @param class-string $class */ public function __construct(string $class) { $this->class = $class; } private function iterate() : void { foreach ($this as $o) {} } }', ], 'extendWithExplicitOverriddenTemplatedSignature' => [ 't1 = $t1; } /** * @return T1 */ public function getValue() { return $this->t1; } } /** * @template T2 as Obj * @template-extends BaseContainer */ class Container extends BaseContainer { /** @param T2 $t2 */ public function __construct($t2) { parent::__construct($t2); } /** * @return T2 */ public function getValue() { return parent::getValue(); } }', ], 'iterateOverExtendedArrayObjectThisClassIterationWithExplicitGetIterator' => [ ' */ public static function getSelfCollection() : Collection { return new Collection(self::class); } public function bar() : void {} } /** * @template T as O * @template-extends ArrayObject */ class Collection extends ArrayObject { /** @var class-string */ public $class; /** @param class-string $class */ public function __construct(string $class) { $this->class = $class; } /** * @return \ArrayIterator */ public function getIterator() { return parent::getIterator(); } } /** @return Collection */ function getFooCollection() : Collection { return new Collection(Foo::class); } foreach (getFooCollection() as $i => $foo) { $foo->bar(); } foreach (Foo::getSelfCollection() as $i => $foo) { $foo->bar(); }', ], 'iterateOverSelfImplementedIterator' => [ ' */ class FooCollection implements Iterator { private function iterate() : void { foreach ($this as $foo) {} } public function current() { return new Foo(); } public function key(): int { return 0; } public function next(): void {} public function rewind(): void {} public function valid(): bool { return false; } }', ], 'extendClassThatParameterizesTemplatedParent' => [ ' */ abstract function elements() : array; /** * @return T|null */ public function first() { return $this->elements()[0] ?? null; } } /** * @template-extends Collection */ abstract class Bridge extends Collection {} class Service extends Bridge { /** * @return array */ public function elements(): array { return [1, 2, 3]; } } $a = (new Service)->first();', [ '$a' => 'int|null', ], ], 'splObjectStorage' => [ ' */ public $handlers; /** * @param SplObjectStorage<\stdClass, mixed> $handlers */ public function __construct(SplObjectStorage $handlers) { $this->handlers = $handlers; } } /** @var SplObjectStorage<\stdClass, string> */ $storage = new SplObjectStorage(); new SomeService($storage); $c = new \stdClass(); $storage[$c] = "hello"; $b = $storage->offsetGet($c);', [ '$b' => 'string', ], ], 'extendsArrayIterator' => [ ' */ class Users extends ArrayIterator { public function __construct(User ...$users) { parent::__construct($users); } }', ], 'genericStaticAndSelf' => [ ' */ public function map(Closure $c); } /** * @template T * @implements Functor */ final class Box implements Functor { /** * @var T */ public $value; /** * @param T $x */ public function __construct($x) { $this->value = $x; } /** * @template U * * @param Closure(T):U $c * * @return self */ public function map(Closure $c) { return new Box($c($this->value)); } }', ], 'useGenericParentMethod' => [ ' */ class Foo extends ArrayObject { public function bar() : void { $c = $this->getArrayCopy(); foreach ($c as $d) { echo $d; } } }', ], 'templateExtendsOnceWithSpecificStaticCall' => [ 't = $t; } /** * @template U * @param U $t * @return static */ public static function getContainer($t) { return new static($t); } /** * @return T */ public function getValue() { return $this->t; } } /** * @template T1 as A * @template-extends Container */ class AContainer extends Container {} class A { function foo() : void {} } $b = AContainer::getContainer(new A());', [ '$b' => 'AContainer', ], ], 'templateExtendsDifferentNameWithStaticCall' => [ 't = $t; } /** * @template U * @param U $t * @return static */ public static function getContainer($t) { return new static($t); } /** * @return T */ public function getValue() { return $this->t; } } /** * @template T1 as object * @template-extends Container */ class ObjectContainer extends Container {} /** * @template T2 as A * @template-extends ObjectContainer */ class AContainer extends ObjectContainer {} class A { function foo() : void {} } $b = AContainer::getContainer(new A());', [ '$b' => 'AContainer', ], ], 'templateExtendsSameNameWithStaticCall' => [ 't = $t; } /** * @template U * @param U $t * @return static */ public static function getContainer($t) { return new static($t); } /** * @return T */ public function getValue() { return $this->t; } } /** * @template T as object * @template-extends Container */ class ObjectContainer extends Container {} /** * @template T as A * @template-extends ObjectContainer */ class AContainer extends ObjectContainer {} class A { function foo() : void {} } $b = AContainer::getContainer(new A());', [ '$b' => 'AContainer', ], ], 'returnParentExtendedTemplateProperty' => [ 't = $t; } } /** * @template-extends Container */ class IntContainer extends Container { public function __construct(int $i) { parent::__construct($i); } public function getValue() : int { return $this->t; } }', ], 'childSetInConstructor' => [ 't = $t; } } /** * @template T1 as object * @template-extends Container */ class ObjectContainer extends Container {}', ], 'grandChildSetInConstructor' => [ 't = $t; } } /** * @template T1 as object * @template-extends Container */ class ObjectContainer extends Container {} /** * @template T2 as A * @template-extends ObjectContainer */ class AContainer extends ObjectContainer {} class A {}', ], 'extendArrayObjectWithTemplateParams' => [ ' */ class C extends \ArrayObject { /** * @param array $kv */ public function __construct(array $kv) { parent::__construct($kv); } } $c = new C(["a" => 1]); $i = $c->getIterator();', [ '$c' => 'C', '$i' => 'ArrayIterator', ], ], 'extendsParamCountDifference' => [ ' */ abstract class Collection implements \Iterator {} /** * @param Collection $collection * @return \Iterator */ function foo(Collection $collection) { return $collection; }', ], 'dontInheritParamTemplatedTypeSameName' => [ ' */ private $t; /** * @param array $t */ public function __construct(array $t) { $this->t = $t; } /** * @inheritdoc */ public function add($t) : void { $this->t[] = $t; } } /** @param C $c */ function foo(C $c) : void { $c->add(new stdClass); }', ], 'dontInheritParamTemplatedTypeDifferentTemplateNames' => [ ' */ private $t; /** * @param array $t */ public function __construct(array $t) { $this->t = $t; } /** * @inheritdoc */ public function add($t) : void { $this->t[] = $t; } } /** @param C $c */ function foo(C $c) : void { $c->add(new stdClass); }', ], 'templateExtendsUnionType' => [ 't = $t; } } /** * @template TT * @template-extends A */ class B extends A {}', ], 'badTemplateImplementsUnionType' => [ ' */ class B implements I { /** @var int|string */ public $t; /** @param int|string $t */ public function __construct($t) { $this->t = $t; } }', ], 'extendWithEnoughArgs' => [ ' */ interface Collection extends IteratorAggregate { } /** * @template T * @template TKey of array-key * @template-implements Collection * @psalm-consistent-constructor */ class ArrayCollection implements Collection { /** * @psalm-var array */ private $elements; /** * @psalm-param array $elements */ public function __construct(array $elements = []) { $this->elements = $elements; } public function getIterator() { return new ArrayIterator($this->elements); } /** * @psalm-suppress MissingTemplateParam * * @psalm-param array $elements * @psalm-return ArrayCollection */ protected function createFrom(array $elements) { return new static($elements); } }', ], 'extendWithTooFewArgs' => [ ' */ interface Collection extends IteratorAggregate { } /** * @psalm-suppress MissingTemplateParam * @template T * @template TKey of array-key * @template-implements Collection * @psalm-consistent-constructor */ class ArrayCollection implements Collection { /** * @psalm-var T[] */ private $elements; /** * @psalm-param array $elements */ public function __construct(array $elements = []) { $this->elements = $elements; } /** * @psalm-suppress InvalidReturnType */ public function getIterator() { /** * @psalm-suppress InvalidReturnStatement */ return new ArrayIterator($this->elements); } /** * @psalm-suppress MissingTemplateParam * * @psalm-param array $elements * @psalm-return ArrayCollection */ protected function createFrom(array $elements) { return new static($elements); } }', ], 'abstractGetIterator' => [ ' */ interface Collection extends \IteratorAggregate { /** * @return \Iterator */ public function getIterator(): \Iterator; } /** * @template-implements Collection */ abstract class Set implements Collection { public function forEach(callable $action): void { $i = $this->getIterator(); foreach ($this as $bar) { $action($bar); } } }', ], 'concreteGetIterator' => [ ' */ interface Collection extends \IteratorAggregate { /** * @return \Iterator */ public function getIterator(); } /** * @template-implements Collection */ class Set implements Collection { public function forEach(callable $action): void { $i = $this->getIterator(); foreach ($this as $bar) { $action($bar); } } public function getIterator() { return new ArrayIterator(["hello"]); } }', ], 'paramInsideTemplatedFunctionShouldKnowRestriction' => [ ' */ class IntHasher implements Hasher { function hash($value): int { return $value % 10; } } /** * @implements Hasher */ class StringHasher implements Hasher { function hash($value): int { return strlen($value); } }', ], 'implementsAndExtendsWithTemplateReturningValid' => [ ' */ function toArray(); } /** * @template TDummy * @implements Collection */ class IntCollection implements Collection { /** @param TDummy $t */ public function __construct($t) { } public function toArray() { return ["foo"]; } }', ], 'templateNotExtendedButSignatureInherited' => [ 'example("str"));', ], 'keyOfClassTemplateExtended' => [ 'data = $data; } /** * @template K as key-of * * @param K $property * * @return TData[K] */ public function __get(string $property) { return $this->data[$property]; } /** * @template K as key-of * * @param K $property * @param TData[K] $value */ public function __set(string $property, $value) { $this->data[$property] = $value; } } /** @extends DataBag */ class FooBag extends DataBag {} $foo = new FooBag(["a" => 5, "b" => "hello"]); $foo->a = 9; $foo->b = "hello"; $a = $foo->a; $b = $foo->b;', [ '$a' => 'int', '$b' => 'string', ], ], 'templateExtendsWithNewlineAfter' => [ ' */ abstract class Foo {} /** * @template-extends Foo * * @internal */ class Bar extends Foo {}', ], 'implementsArrayReturnTypeWithTemplate' => [ ' */ public function indexById($v): array; } /** @template-implements I */ class C implements I { public function indexById($v): array { return [$v => $v]; } }', ], 'keyOfArrayInheritance' => [ ' */ abstract class Foo { /** * @var DATA */ protected $data; /** * @param DATA $data */ public function __construct(array $data) { $this->data = $data; } /** * @return key-of */ abstract public function getIdProperty() : string; } /** * @template-extends Foo */ class FooChild extends Foo { public function getIdProperty() : string { return "id"; } }', ], 'interfaceParentExtends' => [ ' */ interface FooChild extends Foo {} class F implements FooChild { public function getValue() { return 10; } } echo (new F())->getValue();', ], 'classParentExtends' => [ ' */ abstract class FooChild extends Foo {} class F extends FooChild { public function getValue() { return 10; } } echo (new F())->getValue();', ], 'lessSpecificNonGenericReturnType' => [ ' */ class Bar implements IteratorAggregate { public function getIterator() : Traversable { yield from range(0, 100); } } $bat = new Bar(); foreach ($bat as $num) {}', ], 'implictIteratorTemplating' => [ ' */ class SomeIterator implements IteratorAggregate { function getIterator() { yield 1; } } /** @param \IteratorAggregate $i */ function takesIteratorOfInts(\IteratorAggregate $i) : void { foreach ($i as $j) { echo $j; } } takesIteratorOfInts(new SomeIterator());', ], 'genericInterface' => [ ' $t * @return T */ function generic(string $t) { return f($t)->get(); } /** @template T as object */ interface I { /** @return T */ public function get() {} } /** * @template T as object * @template-implements I */ class C implements I { /** * @var T */ public $t; /** * @param T $t */ public function __construct(object $t) { $this->t = $t; } /** * @return T */ public function get() { return $this->t; } } /** * @template T as object * @param class-string $t * @return I * @psalm-suppress MixedMethodCall */ function f(string $t) { return new C(new $t); }', ], 'extendWithExplicitOverriddenTemplatedSignatureHopped' => [ 't1 = $t1; } /** * @return T1 */ public function getValue() { return $this->t1; } } /** * @template T2 * @template-extends Container1 */ class Container2 extends Container1 {} /** * @template T3 as Obj * @template-extends Container2 */ class Container3 extends Container2 { /** @param T3 $t3 */ public function __construct($t3) { Container1::__construct($t3); } /** * @return T3 */ public function getValue() { return parent::getValue(); } }', ], 'extendsArryObjectGetIterator' => [ ' */ class Collection extends ArrayObject {} /** * @template T2 as Obj * @template-extends Collection */ class Collection2 extends Collection { /** * called to get the collection ready when we go to loop through it * * @return \ArrayIterator */ public function getIterator() { return parent::getIterator(); } }', ], 'templatedInterfaceGetIteratorIteration' => [ ' */ interface ICollection extends \IteratorAggregate { /** @return \Traversable */ public function getIterator(); } class Collection implements ICollection { /** @var array */ private $data; public function __construct(array $data) { $this->data = $data; } public function getIterator(): \Traversable { return new \ArrayIterator($this->data); } } /** @var ICollection */ $c = new Collection(["a" => 1]); foreach ($c->getIterator() as $k => $v) { atan($v); strlen($k); }', ], 'extendedPropertyType' => [ ' */ class Test extends C { protected function foo() : void { $this->m = new Impl(); } }' ], 'constructorCheckInChildClassArrayType' => [ ' */ protected $items = []; // added to trigger constructor initialisation checks // in descendant classes public int $i; /** @param array $items */ public function __construct($items = []) { $this->i = 5; foreach ($items as $k => $v) { $this->items[$k] = $v; } } } class Impl implements I {} /** * @template-extends C */ class Test extends C {}' ], 'eitherType' => [ ' */ final class Left implements Either { /** * @param L $value */ public function __construct($value) {} } /** * @template R * @template-implements Either */ final class Right implements Either { /** * @param R $value */ public function __construct($value) {} } class A {} class B {} /** * @return Either */ function result() { if (rand(0, 1)) { return new Left(new A()); } return new Right(new B()); }' ], 'refineGenericWithInstanceof' => [ ' */ class Some implements Maybe { /** @var T */ private $value; /** @psalm-param T $value */ public function __construct($value) { $this->value = $value; } /** @psalm-return T */ public function extract() { return $this->value; } } /** * @psalm-return Maybe */ function repository(): Maybe { return new Some(5); } $maybe = repository(); if ($maybe instanceof Some) { $anInt = $maybe->extract(); }' ], 'extendIterable' => [ '> */ public function provide() { yield [1]; } }' ], 'extendsWithMoreTemplateParams' => [ 't = $t; } /** @return static */ public function getAnother() { return clone $this; } } class MyContainer extends Container {} $a = (new MyContainer("hello"))->getAnother();', ], 'staticClassCreationIndirect' => [ ' $arr */ public function __construct(array $arr) { $this->arr = $arr; } /** * @template T1 as array-key * @template T2 * @param array $arr * @return static */ public static function getInstance(array $arr) { return new static($arr); } /** * @param array $arr * @return static */ public function map(array $arr) { return static::getInstance($arr); } }' ], 'allowExtendingWithTemplatedClass' => [ 't = $t; } } /** * @template T2 */ class Bar { /** @var T2 */ public $t; /** @param T2 $t */ public function __construct($t) { $this->t = $t; } } /** * @template T3 * @extends Bar> */ class BarOfFoo extends Bar { /** @param T3 $t */ public function __construct($t) { parent::__construct(new Foo($t)); } } /** * @template T4 * @param T4 $t * @return Bar> */ function baz($t) { return new BarOfFoo($t); }' ], 'inheritTemplateParamViaConstructorSameName' => [ ' */ protected $arr = []; /** * @param array $arr */ public function __construct(array $arr) { $this->arr = $arr; } } /** * @template T * @template V * @extends Collection */ class CollectionChild extends Collection { } $dogs = new CollectionChild([new Dog(), new Dog()]);', [ '$dogs' => 'CollectionChild' ] ], 'inheritTemplateParamViaConstructorDifferentName' => [ ' */ protected $arr = []; /** * @param array $arr */ public function __construct(array $arr) { $this->arr = $arr; } } /** * @template U * @template V * @extends Collection */ class CollectionChild extends Collection { } $dogs = new CollectionChild([new Dog(), new Dog()]);', [ '$dogs' => 'CollectionChild' ] ], 'extendsClassWithClassStringProperty' => [ ' */ protected $c; } /** * @template T of Some * @extends Y */ class Z extends Y { /** @param class-string $c */ public function __construct(string $c) { $this->c = $c; } }' ], 'implementsParameterisedIterator' => [ ' */ class SelectEntries implements \IteratorAggregate { public function getIterator(): SelectIterator { return new SelectIterator(); } } /** * @implements \Iterator * @psalm-suppress UnimplementedInterfaceMethod */ class SelectIterator implements \Iterator { }' ], 'extendWithExtraParam' => [ ' */ public function slice(int $start, int $length): ICollection; } /** * @template T * * @extends ICollection */ interface IVector extends ICollection { /** * @psalm-return IVector */ public function slice(int $start, int $length): ICollection; }' ], 'concreteDefinesNoSignatureTypes' => [ ' */ abstract class AbstractViewCreator implements IViewCreator { public function view() { return $this->doView(); } /** @return TView */ abstract protected function doView(); } /** * @extends AbstractViewCreator */ class ConcreteViewerCreator extends AbstractViewCreator { protected function doView() { return new ConcreteView; } }' ], 'concreteDefinesSignatureTypes' => [ ' */ abstract class AbstractViewCreator implements IViewCreator { public function view() : IView { return $this->doView(); } /** @return TView */ abstract protected function doView(); } /** * @extends AbstractViewCreator */ class ConcreteViewerCreator extends AbstractViewCreator { protected function doView() { return new ConcreteView; } }' ], 'allowStaticMethodClassTemplates' => [ ' */ public static function messageName(): string; } /** * @template-implements ReplayMessageInterface */ class ReplayDeliveryTimeAggregated implements ReplayMessageInterface { /** * @return class-string */ public static function messageName(): string { return DeliveryTimeAggregated::class; } }' ], 'allowExplicitMethodClassTemplateReturn' => [ ' */ public function m(): string; } /** * @template T2 of object * @template-implements I */ class C implements I { /** @var T2 */ private object $o; /** @param T2 $o */ public function __construct(object $o) { $this->o = $o; } /** * @return class-string */ public function m(): string { return get_class($this->o); } }', ], 'templateInheritanceWithParentTemplateTypes' => [ ' $t * @return ?T2 * @psalm-suppress MixedMethodCall */ public function get($t) { return new $t; } } class AChild extends A { /** * @template T3 * @param class-string $t * @return ?T3 * @psalm-suppress MixedMethodCall */ public function get($t) { return new $t; } }' ], 'extendsInheritingReturnType' => [ ' */ class ContainerSubclass extends Container { /** * @psalm-suppress InvalidReturnType */ public function get(int $key) {} } class MyTestContainerUsage { /** @var ContainerSubclass */ private $container; /** @param ContainerSubclass $container */ public function __construct(ContainerSubclass $container) { $this->container = $container; } public function modify() : void { $this->container->get(1)->foo = 2; } }' ], 'templateYieldFrom' => [ ' */ interface IStringList extends \IteratorAggregate { /** @return \Iterator */ public function getIterator(): \Iterator; } class StringListDecorator implements IStringList { private IStringList $decorated; public function __construct(IStringList $decorated) { $this->decorated = $decorated; } public function getIterator(): \Iterator { yield from $this->decorated; } }' ], 'extendsTemplatedInterface' => [ ' */ interface IStroker2 extends IStroker {} class Dog extends Animal {} /** * @implements IStroker2 */ class DogStroker implements IStroker2 { public function stroke(Animal $animal): void { $this->doDeletePerson($animal); } private function doDeletePerson(Dog $animal): void {} }' ], 'extendsTemplatedClass' => [ ' */ class IStroker2 extends IStroker {} class Dog extends Animal {} /** * @extends IStroker2 */ class DogStroker extends IStroker2 { public function stroke(Animal $animal): void { $this->doDeletePerson($animal); } private function doDeletePerson(Dog $animal): void {} }' ], 'sameNameTemplateFromParent' => [ ' */ public function filter($p) : self; } /** * @psalm-template T * @template-implements C */ abstract class AC implements C { /** * @psalm-var C */ protected $c; public function filter($p) : C { return $this->c->filter($p); } }' ], 'implementsTemplatedTwice' => [ ' */ interface B extends A {} /** * @template T3 * @implements B */ class C implements B { /** @var T3 */ private $val; /** * @psalm-param T3 $val */ public function __construct($val) { $this->val = $val; } public function get() { return $this->val; } } $foo = (new C("foo"))->get();', [ '$foo' => 'string', ] ], 'extendsWithJustParentConstructor' => [ ' */ class EventInstance extends EventAbstract { /** * @template S of Subject * @param S $subject * @return self */ public static function createInstance(Subject $subject) : self { return new self($subject); } } /** * @template T of Subject */ abstract class EventAbstract { /** @var T */ protected $subject; /** * @param T $subject */ public function __construct(Subject $subject) { $this->subject = $subject; } }' ], 'annotationDefinedInInheritedInterface' => [ ' */ interface Y extends X {} /** * @template T3 * @implements Y */ class A implements Y { public function boo($x) { return $x; } } function foo(A $a) : void { $a->boo("boo"); }' ], 'allowPropertyCoercionExtendedParam' => [ ' */ private $c; public function __construct() { $this->c = new ArrayCollection(); $this->c->filter(function (DateTime $dt): bool { return $dt === $dt; }); } } /** * @psalm-template TKey of array-key * @psalm-template T */ interface Collection { /** * @param Closure $p * * @return Collection A * * @psalm-param Closure(T=):bool $p * @psalm-return Collection */ public function filter(Closure $p); } /** * @psalm-template TKey of array-key * @psalm-template T * @template-implements Collection */ class ArrayCollection implements Collection { /** * @psalm-var array * @var array */ private $elements; /** * @param array $elements * * @psalm-param array $elements */ public function __construct(array $elements = []) { $this->elements = $elements; } /** * {@inheritDoc} * * @return static */ public function filter(Closure $p) { return $this; } }' ], 'listTemplating' => [ ' $x * @return T */ public function boo($x); } /** * @template T * @implements X */ class A implements X { public function boo($x) { return $x[0]; } }' ], 'sameNamedTemplateDefinedInParentFunction' => [ 'value = $value; } } interface Temporal { /** * @template T * @param Query $query */ public function execute(Query $query) : void; } /** * @template T */ class Result implements Temporal { /** @var T **/ private $value; /** * @param T $value */ public function __construct($value) { $this->value = $value; } public function execute(Query $query) : void {} } /** * @param Result $result * @param Query $query */ function takesArgs(Result $result, Query $query) : void { $result->execute($query); }' ], 'respectExtendsAnnotationWhenVerifyingFinalChildReturnType' => [ ' */ private $type; /** * @param class-string $type */ public function __construct(string $type) { $this->type = $type; } } abstract class Enum { /** * @return EnumSet */ public static function all() { return new EnumSet(static::class); } } /** * @extends EnumSet */ final class CustomEnumSet extends EnumSet { public function __construct() { parent::__construct(CustomEnum::class); } } final class CustomEnum extends Enum { /** * @return CustomEnumSet */ public static function all() { return new CustomEnumSet(); } }' ], 'allowValidChildReturnType' => [ ' */ private $type; /** * @param class-string $type */ public function __construct(string $type) { $this->type = $type; } } abstract class Enum { /** * @return EnumSet */ public static function all() { return new EnumSet(static::class); } } /** * @extends EnumSet */ final class CustomEnumSet extends EnumSet { public function __construct() { parent::__construct(CustomEnum::class); } } class CustomEnum extends Enum { public static function all() { return new EnumSet(static::class); } }', ], 'extendsWithTemplatedProperty' => [ 'collection = $collection; } } /** * @template I2 as object * * @extends Foo */ class FooChild extends Foo { /** @return I2 */ public function getCollection() { return $this->collection; } }' ], 'setInheritedTemplatedPropertyOutsideClass' => [ 'value = $value; } } /** @extends Watcher */ class IntWatcher extends Watcher {} $watcher = new IntWatcher(0); $watcher->value = 10;' ], 'setRetemplatedPropertyOutsideClass' => [ 'value = $value; } } /** * @template T as scalar * @extends Watcher */ class Watcher2 extends Watcher {} /** @psalm-var Watcher2 $watcher */ $watcher = new Watcher2(0); $watcher->value = 10;' ], 'argInSameLocationShouldHaveConvertedParams' => [ ' */ class X implements I { public function i($argument): void { echo sprintf("%d", $argument); } } /** * @implements I */ class XWithChangedArgumentName implements I { public function i($changedArgumentName): void { echo sprintf("%d", $changedArgumentName); } }' ], 'acceptTemplatedObjectAsStaticParam' => [ 'id = $id; } /** * @param static $id */ final public function equals(self $id): bool { return $this->id === $id->id; } } /** * @template T of Id */ final class Ids { /** * @psalm-var list */ private array $ids; /** * @psalm-param list $ids */ private function __construct(array $ids) { $this->ids = $ids; } /** * @psalm-param T $id */ public function contains(Id $id): bool { foreach ($this->ids as $oneId) { if ($oneId->equals($id)) { return true; } } return false; } }' ], 'nestedTemplateExtends' => [ ' */ abstract class BaseRepository {} class StudentViewData implements IBaseViewData {} class TeacherViewData implements IBaseViewData {} /** @extends BaseModel */ class StudentModel extends BaseModel {} /** @extends BaseModel */ class TeacherModel extends BaseModel {} /** @extends BaseRepository */ class StudentRepository extends BaseRepository {} /** @extends BaseRepository */ class TeacherRepository extends BaseRepository {}' ], 'templateInheritedPropertyCorrectly' => [ 'one = $key; $this->two = $value; } } /** * @template TValue2 * @extends Pair */ class StringKeyedPair extends Pair { /** * @param TValue2 $value */ public function __construct(string $key, $value) { parent::__construct($key, $value); } } $pair = new StringKeyedPair("somekey", 250); $a = $pair->two; $b = $pair->one;', [ '$pair' => 'StringKeyedPair', '$a' => 'int', '$b' => 'string', ] ], 'templateInheritedPropertySameName' => [ 'one = $key; $this->two = $value; } } /** * @template TValue * @extends Pair */ class StringKeyedPair extends Pair { /** * @param TValue $value */ public function __construct(string $key, $value) { parent::__construct($key, $value); } } $pair = new StringKeyedPair("somekey", 250); $a = $pair->two; $b = $pair->one;', [ '$pair' => 'StringKeyedPair', '$a' => 'int', '$b' => 'string', ] ], 'templateInheritedPropertySameNameFlipped' => [ 'one = $key; $this->two = $value; } } /** * @template TValue * @extends Pair */ class StringKeyedPair extends Pair { /** * @param TValue $value */ public function __construct(string $key, $value) { parent::__construct($value, $key); } } $pair = new StringKeyedPair("somekey", 250); $a = $pair->one; $b = $pair->two;', [ '$pair' => 'StringKeyedPair', '$a' => 'int', '$b' => 'string', ] ], 'implementExtendedInterfaceWithMethodOwnTemplateParams' => [ ' */ public static function doFoo($f): self; } /** * @template T3 * @extends IFoo */ interface IFooChild extends IFoo {} /** * @template T5 * @implements IFooChild */ class ConcreteFooChild implements IFooChild { /** @var T5 */ private $baz; /** @param T5 $baz */ public function __construct($baz) { $this->baz = $baz; } /** * @template T6 * @psalm-param T6 $f * @psalm-return ConcreteFooChild */ public static function doFoo($f): self { $r = new self($f); return $r; } }', [], [], '7.4' ], 'implementInterfaceWithMethodOwnTemplateParams' => [ ' */ public static function doFoo($f): self; } /** * @template T5 * @implements IFoo */ class ConcreteFooChild implements IFoo { /** @var T5 */ private $baz; /** @param T5 $baz */ public function __construct($baz) { $this->baz = $baz; } /** * @template T6 * @psalm-param T6 $f * @psalm-return ConcreteFooChild */ public static function doFoo($f): self { $r = new self($f); return $r; } }', [], [], '7.4' ], 'staticShouldBeBoundInCall' => [ ' $item */ public function __construct(string $item) {} } abstract class Vehicle { /** * @return VehicleCollection */ public static function all() { return new VehicleCollection(static::class); } } class Car extends Vehicle {} class CarRepository { /** * @return VehicleCollection */ public function getAllCars(): VehicleCollection { return Car::all(); } }' ], 'templatedParameterIsNotMoreSpecific' => [ ' [ ' */ public function getTypes() { return new Collection(new static); } } final class Cheese extends Food {} /** * @return Collection */ function test(Cheese $cheese): Collection { return $cheese->getTypes(); }' ], 'unwrapExtendedTypeWhileInferring' => [ '> */ final class IC implements I { /** @var T3 */ public $var; /** @param T3 $var */ public function __construct($var) { $this->var = $var; } } /** @template T4 */ final class Container { /** @var I $var */ public I $var; /** @param I $var */ public function __construct(I $var) { $this->var = $var; } } final class Obj {} final class B { /** @return Container> */ public function foo(int $i): Container { $ic = new IC($i); $container = new Container($ic); return $container; } }' ], 'extendIteratorIterator' => [ '> */ abstract class MyFilterIterator extends IteratorIterator { /** @return bool */ public abstract function accept () {} }' ], 'extendedIntoIterable' => [ ' */ interface SubjectCollection extends \IteratorAggregate { /** * @return \Iterator */ public function getIterator(): \Iterator; } /** @param iterable $_ */ function takesSubjects(iterable $_): void {} function givesSubjects(SubjectCollection $subjects): void { takesSubjects($subjects); }' ], 'implementMixedReturnNull' => [ 't = $t; } public function foo() { if (rand(0, 1)) { return null; } return $this->t; } }' ], 'classStringTemplatedExtends' => [ ' */ class StringRequest implements CrudRequest {} /** @template T */ interface CrudNew { /** @param class-string> $requestClass */ public function handle(string $requestClass): void; } class StringNew { /** @param CrudNew $crudNew */ public function foo($crudNew): void { $crudNew->handle(StringRequest::class); } }' ], 'extendTemplateTypeInParamAsType' => [ ' */ final class Apply implements Operation { /** * @return \Closure(array): void */ public function i(): Closure { return /** * @psalm-param array $collection */ static function (array $collection): void{}; } } /** * @template TKey as object */ interface Operation { /** * @psalm-return \Closure(array): void */ public function i(): Closure; }' ], 'extendsWithArraySameObject' => [ '> */ public function zip(): C1; } /** * @template Tv * @extends C1 */ interface C2 extends C1 { /** * @return C2> */ public function zip(): C2; }', [], [], '7.4' ], 'extendsWithArrayDifferentObject' => [ '> */ public function zip(): D1; } /** * @template Tv * @extends C1 */ interface C2 extends C1 { /** * @return D2> */ public function zip(): D2; } /** * @template Tv */ interface D1 {} /** * @template Tv * @extends D1 */ interface D2 extends D1 {}', [], [], '7.4' ], 'allowNestedInterfaceDefinitions' => [ ' */ interface AContainer extends Container { public function get(): A; } interface AContainer2 extends AContainer {} class ConcreteAContainer implements AContainer2 { public function get(): A { return new A(); } }' ], 'paramTypeInheritedWithTemplate' => [ '> */ abstract class ContainerClass extends SimpleClass { /** * @psalm-param Container $param */ abstract public function foo($param): void; } /** * @extends ContainerClass */ abstract class Complex extends ContainerClass { /** * @psalm-param Container $param */ abstract public function foo($param): void; }' ], 'extendAndImplementedTemplatedProperty' => [ 'foo = $foo; } } /** @extends ATestCase */ class BTestCase extends ATestCase { public function getFoo(): B { return $this->foo; } } new BTestCase(new BMock());' ], 'extendAndImplementedTemplatedIntersectionProperty' => [ 'obj = $obj; } } /** @extends ATestCase */ class BTestCase extends ATestCase { public function getFoo(): void { $this->obj->foo(); } }' ], 'extendAndImplementedTemplatedIntersectionReceives' => [ 'obj = $obj; } } /** @extends ATestCase */ class BTestCase extends ATestCase {} new BTestCase(new BMock());' ], 'yieldTemplated' => [ ' */ class Success implements Promise { /** * @psalm-param TValue $value */ public function __construct($value) {} } /** * @psalm-return Generator */ function a(): Generator { return b(yield c()); } function b(string $baz): int { return intval($baz); } /** * @psalm-return Promise */ function c(): Promise { return new Success("a"); }' ], 'multiLineTemplateExtends' => [ ' */ interface WriteModelInterface { /** * Returns the values to be stored when saving. * * @psalm-return D */ public function valuesToSave(): array; } /** * @psalm-immutable * @template D of array * @implements WriteModelInterface */ abstract class WriteModel implements WriteModelInterface { } /** * @extends WriteModelInterface< * array{ * ulid: string, * senderPersonId: int * } * > */ interface EmailWriteModelInterface extends WriteModelInterface { } /** * @psalm-immutable * @extends WriteModel */ final class EmailWriteModel extends WriteModel implements EmailWriteModelInterface { public function valuesToSave(): array { return [ "ulid" => "a string", "senderPersonId" => 1, ]; } }' ], 'inheritCorrectParams' => [ ' [ ' */ public function map(callable $function): Functor; } /** * @template T * @implements Functor */ class FakeFunctor implements Functor { /** * @var T */ private $value; /** * @psalm-param T $value */ public function __construct($value) { $this->value = $value; } public function map(callable $function): Functor { return new FakeFunctor($function($this->value)); } } /** @return Functor */ function foo(string $s) : Functor { $foo = new FakeFunctor($s); $function = function (string $a): int { return strlen($a); }; return $foo->map($function); }' ], ]; } /** * @return iterable */ public function providerInvalidCodeParse(): iterable { return [ 'extendsWithUnfulfilledNonTemplate' => [ ' */ class BarContainer extends Container { /** * @return Foo */ public function getItem() { return new Foo(); } }', 'error_message' => 'ImplementedReturnTypeMismatch - src' . DIRECTORY_SEPARATOR . 'somefile.php:29:36 - The inherited return type \'A\Bar\' for', ], 'extendTemplateAndDoesNotOverrideWithWrongArg' => [ 'id = $id; } /** * @return T */ public function getID() { return $this->id; } } /** * @template-extends User */ class AppUser extends User {} $au = new AppUser("string");', 'error_message' => 'InvalidScalarArgument', ], 'extendsTwiceDifferentNameBrokenChain' => [ 'v = $v; } /** * @return T1 */ public function getValue() { return $this->v; } } /** * @template T2 */ class ChildContainer extends Container {} /** * @template T3 * @template-extends ChildContainer */ class GrandChildContainer extends ChildContainer {} $fc = new GrandChildContainer(5); $a = $fc->getValue();', 'error_message' => 'MixedAssignment', ], 'extendsTwiceSameNameBrokenChain' => [ 'v = $v; } /** * @return T */ public function getValue() { return $this->v; } } /** * @template T */ class ChildContainer extends Container {} /** * @template T * @template-extends ChildContainer */ class GrandChildContainer extends ChildContainer {} $fc = new GrandChildContainer(5); $a = $fc->getValue();', 'error_message' => 'MixedAssignment', ], 'extendsTwiceSameNameLastDoesNotExtend' => [ 'v = $v; } /** * @return T1 */ public function getValue() { return $this->v; } } /** * @template T2 * @template-extends Container */ class ChildContainer extends Container {} class GrandChildContainer extends ChildContainer {} $fc = new GrandChildContainer(5); $a = $fc->getValue();', 'error_message' => 'MixedAssignment', ], 'mismatchingTypesAfterExtends' => [ ' */ class SomeIterator implements IteratorAggregate { /** * @return Traversable */ public function getIterator() { yield new Bar; } }', 'error_message' => 'ImplementedReturnTypeMismatch', ], 'mismatchingTypesAfterExtendsInherit' => [ ' */ class SomeIterator implements IteratorAggregate { public function getIterator() { yield new Bar; } }', 'error_message' => 'InvalidReturnType', ], 'badTemplateExtends' => [ 't = $t; } } /** * @template TT * @template-extends A */ class B extends A {}', 'error_message' => 'UndefinedDocblockClass', ], 'badTemplateExtendsInt' => [ 't = $t; } } /** * @template TT * @template-extends int */ class B extends A {}', 'error_message' => 'InvalidDocblock', ], 'badTemplateExtendsBadFormat' => [ 't = $t; } } /** * @template TT * @template-extends A< > */ class B extends A {}', 'error_message' => 'InvalidDocblock', ], 'badTemplateImplementsShouldBeExtends' => [ 't = $t; } } /** * @template TT * @template-implements A */ class B extends A {}', 'error_message' => 'InvalidDocblock', ], 'badTemplateImplements' => [ ' */ class B implements I {}', 'error_message' => 'UndefinedDocblockClass', ], 'badTemplateImplementsInt' => [ ' 'InvalidDocblock', ], 'badTemplateImplementsBadFormat' => [ ' */ class B implements I {}', 'error_message' => 'InvalidDocblock', ], 'badTemplateExtendsShouldBeImplements' => [ ' */ class B implements I {}', 'error_message' => 'InvalidDocblock', ], 'templateExtendsWithoutAllParams' => [ ' */ class CC extends A {}', 'error_message' => 'MissingTemplateParam', ], 'templateImplementsWithoutAllParams' => [ ' */ class CC implements I {}', 'error_message' => 'MissingTemplateParam', ], 'extendsTemplateButLikeBadly' => [ ' */ class SpecializedByInheritance extends Base {}', 'error_message' => 'InvalidTemplateParam', ], 'doInheritParamTemplatedTypeSameName' => [ ' */ class C implements I { /** @var array */ private $t; /** * @param array $t */ public function __construct(array $t) { $this->t = $t; } /** * @inheritdoc */ public function add($t) : void { $this->t[] = $t; } } /** @param C $c */ function foo(C $c) : void { $c->add(new stdClass); }', 'error_message' => 'InvalidArgument', ], 'doInheritParamTemplatedTypeDifferentTemplateNames' => [ ' */ class C implements I { /** @var array */ private $t; /** * @param array $t */ public function __construct(array $t) { $this->t = $t; } /** * @inheritdoc */ public function add($t) : void { $this->t[] = $t; } } /** @param C $c */ function foo(C $c) : void { $c->add(new stdClass); }', 'error_message' => 'InvalidArgument', ], 'invalidArgumentForInheritedImplementedInterfaceMethodParam' => [ ' */ interface I2 extends I1 {} /** * @template T as array-key * @template-implements I2 */ class C implements I2 { /** @var T */ private $t; /** @param T $t */ public function __construct($t) { $this->t = $t; } public function takeT($t) : void {} } /** @param C $c */ function bar(C $c) : void { $c->takeT(new stdClass); }', 'error_message' => 'InvalidArgument', ], 'implementsAndExtendsWithoutTemplate' => [ ' */ function toArray(); } /** * @implements Collection */ class IntCollection implements Collection { function toArray() { return ["foo"]; } }', 'error_message' => 'InvalidReturnStatement', ], 'implementsAndExtendsWithTemplateReturningInvalid' => [ ' */ function toArray(); } /** * @template TDummy * @implements Collection */ class IntCollection implements Collection { /** @param TDummy $t */ public function __construct($t) { } public function toArray() { return ["foo"]; } }', 'error_message' => 'InvalidReturnStatement', ], 'implementsChildClassWithNonExtendedTemplate' => [ 't = $t; } /** * @param T $x * @return T */ function example($x) { return $x; } } class Child extends Base { function example($x) { return $x; } } /** @param Child $c */ function bar(Child $c) : void { ord($c->example("boris")); }', 'error_message' => 'MixedArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:31:29 - Argument 1 of ord cannot be mixed, expecting string', ], 'preventWiderParentType' => [ ' */ class AStringer extends Stringer { public function getString($t, object $o = null) : string { if ($o) { return parent::getString($o); } return "a"; } }', 'error_message' => 'ArgumentTypeCoercion', ], 'invalidExtendsAnnotation' => [ ' 'InvalidDocblock' ], 'invalidReturnParamType' => [ ' */ class Left implements Either { /** @param L $value */ public function __construct($value) { } } class A {} class B {} /** @return Either */ function result(): Either { return new Left(new B()); }', 'error_message' => 'InvalidReturnStatement' ], 'preventExtendingWithTemplatedClassWithExplicitTypeGiven' => [ 't = $t; } } /** * @template T2 */ class Bar { /** @var T2 */ public $t; /** @param T2 $t */ public function __construct($t) { $this->t = $t; } } /** * @template T3 * @extends Bar> */ class BarOfFoo extends Bar { /** @param T3 $t */ public function __construct($t) { parent::__construct(new Foo($t)); } } /** * @template T4 * @param T4 $t * @return Bar> */ function baz($t) { return new BarOfFoo("hello"); }', 'error_message' => 'InvalidReturnStatement' ], 'noCrashForTooManyTemplateParams' => [ ' */ class DoStuffX implements DoStuff { public function stuff(InterfaceA $object): void {} } final class Foo { /** * @template A of InterfaceA * @psalm-param DoStuff $stuff */ public function __construct(DoStuff $stuff) {} } new Foo(new DoStuffX());', 'error_message' => 'InvalidArgument' ], 'concreteDefinesSignatureTypesDifferent' => [ ' */ abstract class AbstractViewCreator implements IViewCreator { public function view() : IView { return $this->doView(); } /** @return TView */ abstract protected function doView(); } /** * @extends AbstractViewCreator */ class ConcreteViewerCreator extends AbstractViewCreator { protected function doView() { return new OtherConcreteView; } }', 'error_message' => 'InvalidReturnStatement' ], 'preventExplicitMethodClassTemplateReturn' => [ ' */ public function m(): string; } /** * @template T2 of object * @template-implements I */ class C implements I { /** @var T2 */ private object $o; /** @param T2 $o */ public function __construct(object $o) { $this->o = $o; } /** * @return class-string */ public function m(): string { return static::class; } }', 'error_message' => 'LessSpecificReturnStatement' ], 'preventImplicitMethodClassTemplateReturn' => [ ' */ public function m(): string; } /** * @template T2 of object * @template-implements I */ class C implements I { /** @var T2 */ private object $o; /** @param T2 $o */ public function __construct(object $o) { $this->o = $o; } public function m(): string { return static::class; } }', 'error_message' => 'LessSpecificReturnStatement' ], 'preventBadOverrideWhenVerifyingNonFinalChildReturnType' => [ ' */ private $type; /** * @param class-string $type */ public function __construct(string $type) { $this->type = $type; } } abstract class Enum { /** * @return EnumSet */ public static function all() { return new EnumSet(static::class); } } /** * @extends EnumSet */ final class CustomEnumSet extends EnumSet { public function __construct() { parent::__construct(CustomEnum::class); } } class CustomEnum extends Enum { /** * @return CustomEnumSet */ public static function all() { return new CustomEnumSet(); } }', 'error_message' => 'LessSpecificImplementedReturnType' ], 'preventBadLocallyDefinedDocblockWhenVerifyingChildReturnType' => [ ' */ private $type; /** * @param class-string $type */ public function __construct(string $type) { $this->type = $type; } } abstract class Enum { /** * @return EnumSet */ public static function all() { return new EnumSet(static::class); } } /** * @extends EnumSet */ final class CustomEnumSet extends EnumSet { public function __construct() { parent::__construct(CustomEnum::class); } } class CustomEnum extends Enum { /** * @return EnumSet */ public static function all() { return new CustomEnumSet(); } }', 'error_message' => 'LessSpecificReturnStatement' ], 'nestedTemplateExtendsInvalid' => [ ' */ abstract class BaseRepository {} class StudentViewData implements IBaseViewData {} class TeacherViewData implements IBaseViewData {} /** @extends BaseModel */ class StudentModel extends BaseModel {} /** @extends BaseModel */ class TeacherModel extends BaseModel {} /** @extends BaseRepository */ class StudentRepository extends BaseRepository {}', 'error_message' => 'InvalidTemplateParam' ], 'detectIssueInDoublyInheritedMethod' => [ ' */ interface B extends A {} /** * @template T2 * @template-extends B */ interface C extends B {} /** * @param C $c */ function second(C $c) : void { $f = function (FooChild $foo) : FooChild { return $foo; }; $c->test($f); }', 'error_message' => 'ArgumentTypeCoercion' ], ]; } }