,error_levels?:string[]}> */ public function providerValidCodeParse() { 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; } } /** * @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' => '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; } }', ], 'traitUseNotExtended' => [ ' */ 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' => '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 */ 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; } }', ], 'badTemplateUseUnionType' => [ 't = $t; } } /** * @template TT */ class B { /** * @template-use T */ use T; }', ], 'extendWithEnoughArgs' => [ ' */ interface Collection extends IteratorAggregate { } /** * @template T * @template TKey of array-key * @template-implements Collection */ 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 */ class ArrayCollection implements Collection { /** * @psalm-var T[] */ 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); } }', ], '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); } } }', ], '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"));', ], 'allowTraitExtendAndImplementWithExplicitParamType' => [ 'validate($value); $this->value = $value; } /** * @psalm-param T $value * * @param $value */ abstract protected function validate($value): void; } final class StringValidator { /** * @template-use ValueObjectTrait */ use ValueObjectTrait; /** * @param string $value */ protected function validate($value): void { if (strlen($value) > 30) { throw new \Exception("bad"); } } }', ], 'allowTraitExtendAndImplementWithoutExplicitParamType' => [ 'validate($value); $this->value = $value; } /** * @psalm-param T $value * * @param $value */ abstract protected function validate($value): void; } final class StringValidator { /** * @template-use ValueObjectTrait */ use ValueObjectTrait; protected function validate($value): void { if (strlen($value) > 30) { throw new \Exception("bad"); } } }', ], '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 */ 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); }', ], 'traitInImplicitExtendedClass' => [ ' */ class Bar implements Foo { use FooTrait; }', ], '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; } }' ], 'useTraitReturnTypeForInheritedInterface' => [ 'doNormalize($v); } /** * @param TTraitValue $v * @return TTraitNormalizedValue */ abstract protected function doNormalize($v); } /** @implements Normalizer */ class StringNormalizer implements Normalizer { /** @use NormalizerTrait */ use NormalizerTrait; protected function doNormalize($v): string { return trim($v); } }' ], 'useTraitReturnTypeForInheritedClass' => [ 'doNormalize($v); } /** * @param TTraitValue $v * @return TTraitNormalizedValue */ abstract protected function doNormalize($v); } /** @extends Normalizer */ class StringNormalizer extends Normalizer { /** @use NormalizerTrait */ use NormalizerTrait; protected function doNormalize($v): string { return trim($v); } }' ], ]; } /** * @return iterable */ public function providerInvalidCodeParse() { 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', ], 'badTemplateUse' => [ 't = $t; } } /** * @template TT */ class B { /** * @template-use T */ use T; }', 'error_message' => 'UndefinedDocblockClass', ], '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', ], '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' => 'InvalidReturnType', ], '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' => 'InvalidReturnType', ], '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/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' ], 'possiblyNullReferenceOnTraitDefinedMethod' => [ ' */ protected $mocks = []; /** * @param TKey $offset * @return TValue|null * @psalm-suppress LessSpecificImplementedReturnType * @psalm-suppress ImplementedParamTypeMismatch */ public function offsetGet($offset) { return $this->mocks[$offset] ?? null; } } /** * @template TKey as array-key * @template TValue */ interface Arr { /** * @param TKey $offset * @return TValue|null */ public function offsetGet($offset); } /** * @template TKey as array-key * @template TValue * @implements Arr */ class C implements Arr { /** @use T1 */ use T1; /** * @param TKey $offset * @psalm-suppress MixedMethodCall */ public function foo($offset) : void { $this->offsetGet($offset)->bar(); } }', 'error_message' => 'PossiblyNullReference' ], 'possiblyNullReferenceOnTraitDefinedMethodExtended' => [ ' */ protected $mocks = []; /** * @param TKey $offset * @return TValue|null * @psalm-suppress LessSpecificImplementedReturnType * @psalm-suppress ImplementedParamTypeMismatch */ public function offsetGet($offset) { return $this->mocks[$offset] ?? null; } } /** * @template TKey as array-key * @template TValue */ interface Arr { /** * @param TKey $offset * @return TValue|null */ public function offsetGet($offset); } /** * @template TKey as array-key * @template TValue * @implements Arr */ class C implements Arr { /** @use T1 */ use T1; } class D extends C { /** * @param mixed $offset * @psalm-suppress MixedArgument */ public function foo($offset) : void { $this->offsetGet($offset)->bar(); } }', 'error_message' => 'MixedMethodCall' ], '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' ] ]; } }