[ '_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', ] ], 'extendsTwiceSameName' => [ '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; } } /** * @template T2 * @template-extends Container */ class ChildContainer extends Container {} /** * @template T3 * @template-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' => 'null|SpecificEntity', ] ], 'templateExtendsTwiceAndBound' => [ ' */ class CommonAppRepo extends Repo {} class SpecificEntity {} /** @template-extends CommonAppRepo */ class SpecificRepo extends CommonAppRepo {} $a = new SpecificRepo(); $b = $a->findOne();', [ '$a' => 'SpecificRepo', '$b' => 'null|SpecificEntity', ] ], '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(); }', ], '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) {} } }', ], '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() { /** @var ArrayIterator */ 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; } }', ], 'traitUse' => [ ' */ abstract function elements() : array; /** * @return T|null */ public function first() { return $this->elements()[0] ?? null; } } class Service { /** * @use CollectionTrait */ use CollectionTrait; /** * @return array */ public function elements(): array { return [1, 2, 3, 4]; } }', ], 'extendedTraitUse' => [ ' */ abstract function elements() : array; /** * @return T|null */ public function first() { return $this->elements()[0] ?? null; } } /** * @template TValue */ trait BridgeTrait { /** * @use CollectionTrait */ use CollectionTrait; } class Service { /** * @use BridgeTrait */ use BridgeTrait; /** * @return array */ public function elements(): array { return [1, 2, 3, 4]; } }', ], 'extendedTraitUseAlreadyBound' => [ ' */ abstract function elements() : array; /** * @return T|null */ public function first() { return $this->elements()[0] ?? null; } } trait BridgeTrait { /** * @use CollectionTrait */ use CollectionTrait; } class Service { use BridgeTrait; /** * @return array */ public function elements(): array { return [1, 2, 3, 4]; } }', ], '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' => 'null|int', ] ], '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); } }', ], 'extendsAndCallsParent' => [ ' */ class Bar extends Foo { /** * @param class-string $str * * @return class-string */ public static function DoThing(string $str) { return parent::DoThing($str); } }' ], ]; } /** * @return array */ public function providerInvalidCodeParse() { return [ 'extendsWithUnfulfilledNonTemplate' => [ ' */ class BarContainer extends Container { /** * @return Foo */ public function getItem() { return new Foo(); } }', 'error_message' => 'ImplementedReturnTypeMismatch - src/somefile.php:29 - The 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 T */ public function getValue() { return $this->v; } } /** * @template T * @template-extends Container */ class ChildContainer extends Container {} /** * @template T */ 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' => 'UndefinedClass' ], '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' ], 'badTemplateExtendsUnionType' => [ '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' => 'UndefinedClass' ], 'badTemplateImplementsInt' => [ ' 'InvalidDocblock' ], 'badTemplateImplementsBadFormat' => [ ' */ class B implements I {}', 'error_message' => 'InvalidDocblock' ], 'badTemplateImplementsUnionType' => [ ' */ class B implements I {}', 'error_message' => 'InvalidDocblock' ], 'badTemplateExtendsShouldBeImplements' => [ ' */ class B implements I {}', 'error_message' => 'InvalidDocblock' ], 'badTemplateUse' => [ 't = $t; } } /** * @template TT */ class B { /** * @template-use T */ use T; }', 'error_message' => 'UndefinedClass' ], 'badTemplateUseBadFormat' => [ 't = $t; } } /** * @template TT */ class B { /** * @template-use T< > */ use T; }', 'error_message' => 'InvalidDocblock' ], 'badTemplateUseInt' => [ 't = $t; } } /** * @template TT */ class B { /** * @template-use int */ use T; }', 'error_message' => 'InvalidDocblock' ], 'badTemplateExtendsShouldBeUse' => [ 't = $t; } } /** * @template TT */ class B { /** * @template-extends T */ use T; }', 'error_message' => 'InvalidDocblock' ], 'badTemplateUseUnionType' => [ 't = $t; } } /** * @template TT */ class B { /** * @template-use T */ use T; }', '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' ], ]; } }