,error_levels?:string[]}> */ public function providerValidCodeParse() { return [ 'classTemplate' => [ 'T = $T; } /** * @return T */ public function bar() { $t = $this->T; return new $t(); } } $at = "A"; /** @var Foo */ $afoo = new Foo($at); $afoo_bar = $afoo->bar(); $bfoo = new Foo(B::class); $bfoo_bar = $bfoo->bar(); // this shouldn’t cause a problem as it’s a docbblock type if (!($bfoo_bar instanceof B)) {} $c = C::class; $cfoo = new Foo($c); $cfoo_bar = $cfoo->bar();', 'assertions' => [ '$afoo' => 'Foo', '$afoo_bar' => 'A', '$bfoo' => 'Foo', '$bfoo_bar' => 'B', '$cfoo' => 'Foo', '$cfoo_bar' => 'C', ], 'error_levels' => [ 'MixedReturnStatement', 'LessSpecificReturnStatement', 'DocblockTypeContradiction', 'TypeCoercion', ], ], 'classTemplateSelfs' => [ 'T = $T; } /** * @return T */ public function bar() { $t = $this->T; return new $t(); } } class E { /** * @return Foo */ public static function getFoo() { return new Foo(__CLASS__); } /** * @return Foo */ public static function getFoo2() { return new Foo(self::class); } /** * @return Foo */ public static function getFoo3() { return new Foo(static::class); } } class G extends E {} $efoo = E::getFoo(); $efoo2 = E::getFoo2(); $efoo3 = E::getFoo3(); $gfoo = G::getFoo(); $gfoo2 = G::getFoo2(); $gfoo3 = G::getFoo3();', 'assertions' => [ '$efoo' => 'Foo', '$efoo2' => 'Foo', '$efoo3' => 'Foo', '$gfoo' => 'Foo', '$gfoo2' => 'Foo', '$gfoo3' => 'Foo', ], 'error_levels' => [ 'LessSpecificReturnStatement', 'RedundantConditionGivenDocblockType', ], ], 'classTemplateExternalClasses' => [ 'T = $T; } /** * @return T */ public function bar() { $t = $this->T; return new $t(); } } $efoo = new Foo(\Exception::class); $efoo_bar = $efoo->bar(); $ffoo = new Foo(\LogicException::class); $ffoo_bar = $ffoo->bar();', 'assertions' => [ '$efoo' => 'Foo', '$efoo_bar' => 'Exception', '$ffoo' => 'Foo', '$ffoo_bar' => 'LogicException', ], 'error_levels' => ['LessSpecificReturnStatement'], ], 'classTemplateContainer' => [ 'obj = $obj; } /** * @return T */ public function bar() { return $this->obj; } /** * @return T */ public function bat() { return $this->bar(); } public function __toString(): string { return "hello " . $this->obj; } } $afoo = new Foo(new A()); $afoo_bar = $afoo->bar();', 'assertions' => [ '$afoo' => 'Foo', '$afoo_bar' => 'A', ], 'error_levels' => ['MixedOperand'], ], 'validTemplatedType' => [ ' [ ' [ 'bar(new A());', ], 'validTemplatedStaticMethodType' => [ ' [ 'foo("string"));', ], 'genericArrayKeys' => [ ' $arr * @return array */ function my_array_keys($arr) { return array_keys($arr); } $a = my_array_keys(["hello" => 5, "goodbye" => new \Exception()]);', 'assertions' => [ '$a' => 'array', ], ], 'genericArrayFlip' => [ ' $arr * @return array */ function my_array_flip($arr) { return array_flip($arr); } $b = my_array_flip(["hello" => 5, "goodbye" => 6]);', 'assertions' => [ '$b' => 'array', ], ], 'byRefKeyValueArray' => [ ' $arr */ function byRef(array &$arr) : void {} $b = ["a" => 5, "c" => 6]; byRef($b);', 'assertions' => [ '$b' => 'array', ], ], 'byRefMixedKeyArray' => [ ' $arr */ function byRef(array &$arr) : void {} $b = ["a" => 5, "c" => 6]; byRef($b);', 'assertions' => [ '$b' => 'array', ], ], 'mixedArrayPop' => [ ' $arr * @return TValue|null */ function my_array_pop(array &$arr) { return array_pop($arr); } /** @var mixed */ $b = ["a" => 5, "c" => 6]; $a = my_array_pop($b);', 'assertions' => [ '$a' => 'mixed', '$b' => 'array', ], 'error_levels' => ['MixedAssignment', 'MixedArgument'], ], 'genericArrayPop' => [ ' $arr * @return TValue|null */ function my_array_pop(array &$arr) { return array_pop($arr); } $b = ["a" => 5, "c" => 6]; $a = my_array_pop($b);', 'assertions' => [ '$a' => 'null|int', '$b' => 'array', ], ], 'intersectionTemplatedTypes' => [ ' */ private $data; /** @psalm-param iterable $data */ public function __construct(iterable $data) { $this->data = $data; } } class Item {} /** @psalm-param Collection $c */ function takesCollectionOfItems(Collection $c): void {} /** @psalm-var iterable $data2 */ $data2 = []; takesCollectionOfItems(new Collection($data2)); /** @psalm-var iterable&Countable $data */ $data = []; takesCollectionOfItems(new Collection($data));', ], 'templateCallableReturnType' => [ ' [ ' [ ' */ private $data; /** @param array $data */ public function __construct(array $data) { $this->data = $data; } /** * @template T * @param Closure(TValue):T $func * @return ArrayCollection */ public function map(Closure $func) { return new static(array_map($func, $this->data)); } } class Item {} /** * @param ArrayCollection $i */ function takesCollectionOfItems(ArrayCollection $i): void {} $c = new ArrayCollection([ new Item ]); takesCollectionOfItems($c); takesCollectionOfItems($c->map(function(Item $i): Item { return $i;})); takesCollectionOfItems($c->map(function(Item $i): Item { return $i;}));', ], 'replaceChildTypeWithGenerator' => [ ' $t * @return array */ function f(Traversable $t): array { $ret = []; foreach ($t as $k => $v) $ret[$k] = $v; return $ret; } /** @return Generator */ function g():Generator { yield new stdClass; } takesArrayOfStdClass(f(g())); /** @param array $p */ function takesArrayOfStdClass(array $p): void {}', ], 'noRepeatedTypeException' => [ ' */ private $items; /** * @param class-string $type * @template-typeof T $type */ public function __construct(string $type) { if (!in_array($type, [A::class, B::class], true)) { throw new \InvalidArgumentException; } $this->type = $type; $this->items = []; } /** @param T $item */ public function add($item): void { $this->items[] = $item; } } class FooFacade { /** * @template T * @param T $item */ public function add($item): void { $foo = $this->ensureFoo([$item]); $foo->add($item); } /** * @template T * @param array $items * @return Foo */ private function ensureFoo(array $items): Foo { $type = $items[0] instanceof A ? A::class : B::class; return new Foo($type); } } class A {} class B {}', ], 'collectionOfClosure' => [ ' * @psalm-suppress MixedTypeCoercion */ public function filter(Closure $p) { return $this; } } class I {} /** @var Collection> $c */ $c = new Collection; $c->filter( /** @param Collection $elt */ function(Collection $elt): bool { return (bool) rand(0,1); } ); $c->filter( /** @param Collection $elt */ function(Collection $elt): bool { return true; } );', ], 'splatTemplateParam' => [ ' $arr * @param array $arr2 * @return array */ function splat_proof(array $arr, array $arr2) { return $arr; } $foo = [ [1, 2, 3], [1, 2], ]; $a = splat_proof(...$foo);', 'assertions' => [ '$a' => 'array', ], ], 'passArrayByRef' => [ ' $_arr * @return null|TValue * @psalm-ignore-nullable-return */ function fRef(array &$_arr) { return array_shift($_arr); } /** * @template TKey as array-key * @template TValue * * @param array $_arr * @return null|TValue * @psalm-ignore-nullable-return */ function fNoRef(array $_arr) { return array_shift($_arr); }', ], 'templatedInterfaceIteration' => [ ' */ public function getIterator(); } class Collection implements ICollection { /** @var array */ private $data; public function __construct(array $data) { $this->data = $data; } /** @psalm-suppress LessSpecificImplementedReturnType */ public function getIterator(): \Traversable { return new \ArrayIterator($this->data); } } /** @var ICollection */ $c = new Collection(["a" => 1]); foreach ($c as $k => $v) { atan($v); strlen($k); }', ], 'templatedInterfaceGetIteratorIteration' => [ ' */ public function getIterator(); } class Collection implements ICollection { /** @var array */ private $data; public function __construct(array $data) { $this->data = $data; } /** @psalm-suppress LessSpecificImplementedReturnType */ 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); }', ], 'allowTemplatedIntersectionToExtend' => [ 'bar = $bar; } /** * @return T&Foo */ public function makeFoo() { return $this->bar; } }', ], 'restrictTemplateInputWithTClassGoodInput' => [ ' */ private $items; /** * @param T::class $type */ public function __construct(string $type) { if (!in_array($type, [A::class, B::class], true)) { throw new \InvalidArgumentException; } $this->type = $type; $this->items = []; } /** @param T $item */ public function add($item): void { $this->items[] = $item; } } class A {} class B {} $foo = new Foo(A::class); $foo->add(new A);', ], 'classTemplateAsCorrect' => [ ' [ ' [ ' [ 't = $t; } /** * @return T */ public function getFoo() { return $this->t; } } function passFoo(Foo $f) : Foo { return (new FooGetter($f))->getFoo(); }', ], 'templateFunctionVar' => [ 'bar(); /** @var T&D */ $b = $some_t; $b->bar(); /** @var D&T */ $b = $some_t; $b->bar(); return $a; }', 'assertions' => [], 'error_levels' => ['MixedAssignment', 'MissingParamType'], ], 'returnClassString' => [ ' [ ' [], 'error_levels' => ['MixedMethodCall'], ], 'returnTemplatedClassClassName' => [ ' $class * @return T|null */ public function loader(string $class) { return $class::load(); } } class Foo { /** @return static */ public static function load() { return new static(); } } class FooChild extends Foo{} $a = (new I)->loader(FooChild::class);', 'assertions' => [ '$a' => 'null|FooChild', ], ], 'upcastIterableToTraversable' => [ ' [], 'error_levels' => ['MixedAssignment'], ], 'upcastGenericIterableToGenericTraversable' => [ ' * @param T::class $class */ function foo(string $class) : void { $a = new $class(); foreach ($a as $b) {} }', 'assertions' => [], 'error_levels' => [], ], 'bindFirstTemplatedClosureParameter' => [ ' [ ' */ private $type; /** * @param class-string $type */ public function __construct(string $type) { $this->type = $type; } /** * @return class-string */ public function getType() { return $this->type; } /** * @param T $object */ public function bar(Foo $object) : void { if ($this->getType() !== get_class($object)) { return; } echo $object->id; } } class FooChild extends Foo {} /** @param Collection $c */ function handleCollectionOfFoo(Collection $c) : void { if ($c->getType() === FooChild::class) {} }', ], 'getEquateClass' => [ 'obj = $obj; } /** * @param T $object */ public function bar(Foo $object) : void { if ($this->obj === $object) {} } }', ], 'allowComparisonGetTypeResult' => [ ' */ private $type; /** * @param class-string $type */ public function __construct(string $type) { $this->type = $type; } /** * @return class-string|null */ public function getType() { return $this->type; } } function foo(Collection $c) : void { $val = $c->getType(); if (!$val) {} if ($val) {} }', ], 'mixedTemplatedParamOutWithNoExtendedTemplate' => [ 'v = $v; } /** * @return TValue */ public function getValue() { return $this->v; } } /** * @template TKey * @template TValue */ 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' => 'mixed', ], 'error_levels' => ['MixedAssignment'], ], 'mixedTemplatedParamOutDifferentParamName' => [ 'v = $v; } /** * @return TValue */ public function getValue() { return $this->v; } } /** * @template TKey * @template Tv */ 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' => 'mixed', ], 'error_levels' => ['MixedAssignment'], ], 'doesntExtendTemplateAndDoesNotOverride' => [ 'id = $id; } /** * @return T */ public function getID() { return $this->id; } } class AppUser extends User {} $au = new AppUser(-1); $id = $au->getId();', [ '$au' => 'AppUser', '$id' => 'array-key', ], ], 'callableReturnsItself' => [ ' [ ' [ 'getIterator();', [ '$i' => 'Traversable', ], ], 'upcastArrayToIterable' => [ ' $collection * @return V * @psalm-suppress InvalidReturnType */ function first($collection) {} $one = first([1,2,3]);', [ '$one' => 'int', ], ], 'templateObjectLikeValues' => [ ',1:Collection} * @psalm-suppress InvalidReturnType */ public function partition() {} } /** @var Collection $c */ $c = new Collection; [$partA, $partB] = $c->partition();', [ '$partA' => 'Collection', '$partB' => 'Collection', ], ], 'understandTemplatedCalculationInOtherFunction' => [ ' [ 'add(5, "hello"); $list->add("hello", 5); /** @var SplDoublyLinkedList */ $templated_list = new SplDoublyLinkedList(); $templated_list->add(5, "hello"); $a = $templated_list->bottom();', [ '$a' => 'string', ], ], 'objectReturn' => [ ' $foo * * @return T */ function Foo(string $foo) : object { return new $foo; } echo Foo(DateTime::class)->format("c");', ], 'templateIntersectionLeft' => [ ' [ ' [ '|TReturn) $gen * @return array */ function call(callable $gen) : array { $return = $gen(); if ($return instanceof Generator) { return [$gen->getReturn()]; } return [$gen]; } $arr = call( /** * @return Generator */ function() { yield 1; return "hello"; } );', [ '$arr' => 'array', ], ], 'templatedClassStringParamAsClass' => [ ' $c_class * * @return C * @psalm-return T */ public static function get(string $c_class) : C { $c = new $c_class; $c->foo(); return $c; } } /** * @param class-string $c_class */ function bar(string $c_class) : void { $c = E::get($c_class); $c->foo(); } /** * @psalm-suppress TypeCoercion */ function bat(string $c_class) : void { $c = E::get($c_class); $c->foo(); }', ], 'templatedClassStringParamAsObject' => [ ' $c_class * * @psalm-return T */ public static function get(string $c_class) { return new $c_class; } } /** * @psalm-suppress TypeCoercion */ function bat(string $c_class) : void { $c = E::get($c_class); $c->bar = "bax"; }', ], 'templatedClassStringParamMoreSpecific' => [ ' $c_class * * @return C * @psalm-return T */ public static function get(string $c_class) : C { $c = new $c_class; $c->foo(); return $c; } } /** * @param class-string $d_class */ function moreSpecific(string $d_class) : void { $d = E::get($d_class); $d->foo(); $d->faa(); }', ], 'templateOfWithSpace' => [ ' */ class Foo { } /** * @param Foo> $a */ function bar(Foo $a) : void {}', ], 'templateDefaultSimpleString' => [ 't = $t; } } $c = new C();', 'assertions' => [ '$c===' => 'C', ], ], 'SKIPPED-templateDefaultConstant' => [ 't = $t; } } $e = new E();', 'assertions' => [ '$e===' => 'E', ], ], 'SKIPPED-templateDefaultClassConstant' => [ 't = $t; } } $e = new E();', 'assertions' => [ '$e===' => 'E', ], ], 'allowNullablePropertyAssignment' => [ ' $foo */ public function __construct(I $foo) { $this->bar = $foo->get(); } }', ], 'allowUnionTypeParam' => [ ' $y */ function example($x, $y): void {} example( /** * @param int|false $x */ function($x): void {}, [strpos("str", "str")] );', ], 'reflectionClass' => [ ' $name */ class CustomReflectionClass { /** * @var class-string */ public $name; /** * @param T|class-string $argument */ public function __construct($argument) { if (is_object($argument)) { $this->name = get_class($argument); } else { $this->name = $argument; } } } /** * @template T as object * @param class-string $className * @return CustomReflectionClass */ function getTypeOf(string $className) { return new CustomReflectionClass($className); }', ], 'ignoreTooManyArrayArgs' => [ ' */ $b = [1, 2, 3]; takesArray($b);', ], 'ignoreTooManyGenericObjectArgs' => [ 't = $t; } } /** @param C $c */ function takesC(C $c) : void {} /** * @psalm-suppress TooManyTemplateParams * @var C */ $c = new C(5); takesC($c);', ], 'classTemplateUnionType' => [ ' $c */ function foo(C $c) : void {} /** @param C $c */ function bar(C $c) : void {}', ], 'functionTemplateUnionType' => [ ' [ '$s' => 'string', '$i' => 'int', ], ], 'unionAsTypeReturnType' => [ ' */ public function filter(Closure $p); }', ], 'converterObject' => [ ' $records */ public $records; /** * @param array $records */ public function __construct(array $records) { $this->records = $records; } /** * @template Q2 as object * * @param Q2 $obj2 * * @return array */ private function appender(object $obj2): array { $arr = []; foreach ($this->records as $key => $obj) { if (rand(0, 1)) { $obj = $obj2; } $arr[$key] = $obj; } return $arr; } /** * @template Q1 as object * * @param Q1 $obj * * @return array */ public function appendProperty(object $obj): array { return $this->appender($obj); } }', ], 'converterClassString' => [ ' $records */ public $records; /** * @param array $records */ public function __construct(array $records) { $this->records = $records; } /** * @template Q as object * * @param class-string $obj * * @return array */ public function appendProperty(string $obj): array { return $this->appender($obj); } /** * @template Q as object * * @param class-string $obj2 * * @return array */ private function appender(string $obj2): array { $arr = []; foreach ($this->records as $key => $obj) { if (rand(0, 1)) { $obj = new $obj2; } $arr[$key] = $obj; } return $arr; } }', ], 'allowTemplateReconciliation' => [ ' [ ' */ private $elements; /** * @param array $elements */ public function __construct(array $elements = []) { $this->elements = $elements; } } /** @psalm-suppress MixedArgument */ $c = new ArrayCollection($_GET["a"]);', [ '$c' => 'ArrayCollection', ], ], 'doNotCombineTypes' => [ 't = $t; } /** * @return T */ public function get() { return $this->t; } } /** * @param C $a * @param C $b * @return C|C */ function randomCollection(C $a, C $b) : C { if (rand(0, 1)) { return $a; } return $b; } $random_collection = randomCollection(new C(new A), new C(new B)); $a_or_b = $random_collection->get();', [ '$random_collection' => 'C|C', '$a_or_b' => 'B|A', ], ], 'inferClosureParamTypeFromContext' => [ ' */ function map(callable $action): self; } /** * @template T */ interface Optional { /** * @return T */ function get(); } /** * @param Collection> $collection * @return Collection */ function expandOptions(Collection $collection) : Collection { return $collection->map( function ($optional) { return $optional->get(); } ); }', ], 'reconcileTraversableTemplatedAndNormal' => [ 'getIterator(); $t = $a; } if (!$t instanceof Iterator) { return; } if (rand(0, 1) && rand(0, 1)) { $t->next(); } }', ], 'templateArrayIntersection' => [ ' $a * @param class-string $type * @return array */ function filter(array $a, string $type): array { $result = []; foreach ($a as $item) { if (is_a($item, $type)) { $result[] = $item; } } return $result; } interface A {} interface B {} /** @var array */ $x = []; $y = filter($x, B::class);', [ '$y' => 'array', ] ], 'templateEmptyParamCoercion' => [ ' */ private $data; /** @psalm-param iterable $data */ public function __construct(iterable $data = []) { $this->data = $data; } } class Item {} /** @psalm-param Collection $c */ function takesCollectionOfItems(Collection $c): void {} takesCollectionOfItems(new Collection()); takesCollectionOfItems(new Collection([]));', ], 'templatedGet' => [ ' */ protected $data = []; /** @param array $data */ public function __construct(array $data) { $this->data = $data; } /** @param P $name */ public function __isset(string $name): bool { return isset($this->data[$name]); } /** * @param P $name * @return V */ public function __get(string $name) { return $this->data[$name]; } } $p = new PropertyBag(["a" => "data for a", "b" => "data for b"]); $a = $p->a;', [ '$a' => 'string' ] ], 'templateAsArray' => [ ' */ abstract class Foo { /** * @var DATA */ protected $data; /** * @param DATA $data */ public function __construct(array $data) { $this->data = $data; } /** * @return scalar|array|object|null */ public function __get(string $property) { return $this->data[$property] ?? null; } /** * @param scalar|array|object|null $value */ public function __set(string $property, $value) { $this->data[$property] = $value; } }', ], 'keyOfTemplate' => [ ' * * @param T $o * @param K $name * * @return T[K] */ function getOffset(array $o, $name) { return $o[$name]; } $a = ["foo" => "hello", "bar" => 2]; $b = getOffset($a, "foo"); $c = getOffset($a, "bar");', [ '$b' => 'string', '$c' => 'int', ] ], 'keyOfClassTemplateAcceptingIndexedAccess' => [ 'data = $data; } /** * @template K as key-of * * @param K $property * @param TData[K] $value */ public function __set(string $property, $value) { $this->data[$property] = $value; } }' ], 'keyOfClassTemplateReturningIndexedAccess' => [ 'data = $data; } /** * @template K as key-of * * @param K $property * * @return TData[K] */ public function __get(string $property) { return $this->data[$property]; } }' ], 'unionTOrClassStringTPassedClassString' => [ ' $someType * @psalm-return T */ function getObject($someType) { if (is_object($someType)) { return $someType; } return new $someType(); } class C { function sayHello() : string { return "hi"; } } getObject(C::class)->sayHello();' ], 'unionTOrClassStringTPassedObject' => [ ' $someType * @psalm-return T */ function getObject($someType) { if (is_object($someType)) { return $someType; } return new $someType(); } class C { function sayHello() : string { return "hi"; } } getObject(new C())->sayHello();' ], 'SKIPPED-templatedInterfaceIntersectionFirst' => [ '&IChild */ function makeConcrete() : IChild { return new class() implements IChild { public function foo() { return new C(); } }; } $a = makeConcrete()->foo();', [ '$a' => 'C', ] ], 'templatedInterfaceIntersectionSecond' => [ ' */ function makeConcrete() : IChild { return new class() implements IChild { public function foo() { return new C(); } }; } $a = makeConcrete()->foo();', [ '$a' => 'C', ] ], 'returnTemplateIntersectionGenericObjectAndTemplate' => [ ' $className * * @psalm-return T&I */ function makeConcrete(string $className) : object { return new class() extends C implements I { public function getMe() { return $this; } }; } $a = makeConcrete(C::class);', [ '$a' => 'C&I' ] ], 'dontModifyByRefTemplatedArray' => [ ' $className * @param array $map * @param-out array $map * @param int $id * @return T */ function get(string $className, array &$map, int $id) { if(!array_key_exists($id, $map)) { $map[$id] = new $className(); } return $map[$id]; } /** * @param array $mapA */ function getA(int $id, array $mapA): A { return get(A::class, $mapA, $id); } /** * @param array $mapB */ function getB(int $id, array $mapB): B { return get(B::class, $mapB, $id); }' ], 'dontGeneraliseBoundParamWithWiderCallable' => [ ' 'C', ] ], 'unionClassStringTWithTReturnsObjectWhenCoerced' => [ ' $s * @return T */ function bar($s) { if (is_object($s)) { return $s; } return new $s(); } function foo(string $s) : object { /** @psalm-suppress ArgumentTypeCoercion */ return bar($s); }' ], 'keyOfArrayGet' => [ ' */ abstract class Foo { /** * @var DATA */ protected $data; /** * @param DATA $data */ public function __construct(array $data) { $this->data = $data; } /** * @template K as key-of * * @param K $property * * @return DATA[K] */ public function __get(string $property) { return $this->data[$property]; } }', ], 'keyOfArrayRandomKey' => [ ' */ abstract class Foo { /** * @var DATA */ protected $data; /** * @param DATA $data */ public function __construct(array $data) { $this->data = $data; } /** * @return key-of */ abstract public function getRandomKey() : string; }' ], 'allowBoolTemplateCoercion' => [ ' */ function test(): TestPromise { return new TestPromise(true); }', ], 'allowTemplatedIntersectionFirst' => [ ' $className * @psalm-return RequestedType&MockObject * @psalm-suppress MixedInferredReturnType * @psalm-suppress MixedReturnStatement */ function mock(string $className) { eval(\'"there be dragons"\'); return $instance; } class A { public function foo() : void {} } /** * @psalm-template UnknownType * @psalm-param class-string $className */ function useMockTemplated(string $className) : void { mock($className)->checkExpectations(); } mock(A::class)->foo();' ], 'allowTemplatedIntersectionFirstTemplatedMock' => [ ' $className * @psalm-return RequestedType&MockObject * @psalm-suppress MixedInferredReturnType * @psalm-suppress MixedReturnStatement */ function mock(string $className) { eval(\'"there be dragons"\'); return $instance; } class A { public function foo() : void {} } /** * @psalm-template UnknownType * @psalm-param class-string $className */ function useMockTemplated(string $className) : void { mock($className)->checkExpectations(); } mock(A::class)->foo();' ], 'allowTemplatedIntersectionSecond' => [ ' $className * @psalm-return MockObject&RequestedType * @psalm-suppress MixedInferredReturnType * @psalm-suppress MixedReturnStatement */ function mock(string $className) { eval(\'"there be dragons"\'); return $instance; } class A { public function foo() : void {} } /** * @psalm-param class-string $className */ function useMock(string $className) : void { mock($className)->checkExpectations(); } /** * @psalm-template UnknownType * @psalm-param class-string $className */ function useMockTemplated(string $className) : void { mock($className)->checkExpectations(); } mock(A::class)->foo();' ], 'allowTemplateTypeBeingUsedInsideFunction' => [ ' [ ' */ public function providerInvalidCodeParse() { return [ 'invalidTemplatedType' => [ ' 'InvalidScalarArgument', ], 'invalidTemplatedStaticMethodType' => [ ' 'InvalidScalarArgument', ], 'invalidTemplatedInstanceMethodType' => [ 'foo(4));', 'error_message' => 'InvalidScalarArgument', ], 'replaceChildTypeNoHint' => [ ' $t * @return array */ function f(Traversable $t): array { $ret = []; foreach ($t as $k => $v) $ret[$k] = $v; return $ret; } function g():Generator { yield new stdClass; } takesArrayOfStdClass(f(g())); /** @param array $p */ function takesArrayOfStdClass(array $p): void {}', 'error_message' => 'MixedArgumentTypeCoercion', ], 'restrictTemplateInputWithClassString' => [ ' */ private $items; /** * @param T::class $type */ public function __construct(string $type) { if (!in_array($type, [A::class, B::class], true)) { throw new \InvalidArgumentException; } $this->type = $type; $this->items = []; } /** @param T $item */ public function add($item): void { $this->items[] = $item; } } class A {} class B {} $foo = new Foo(A::class); $foo->add(new B);', 'error_message' => 'InvalidArgument', ], 'restrictTemplateInputWithTClassBadInput' => [ ' */ private $items; /** * @param T::class $type */ public function __construct(string $type) { if (!in_array($type, [A::class, B::class], true)) { throw new \InvalidArgumentException; } $this->type = $type; $this->items = []; } /** @param T $item */ public function add($item): void { $this->items[] = $item; } } class A {} class B {} $foo = new Foo(A::class); $foo->add(new B);', 'error_message' => 'InvalidArgument', ], 'templatedClosureProperty' => [ ' 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:20:34 - Argument 1 of type expects string, callable(State):(T as mixed)&Foo provided', ], 'classTemplateAsIncorrectClass' => [ ' 'InvalidArgument', ], 'classTemplateAsIncorrectInterface' => [ ' 'InvalidArgument', ], 'templateFunctionMethodCallWithoutMethod' => [ 'bar(); }', 'error_message' => 'PossiblyUndefinedMethod', ], 'templateFunctionMethodCallWithoutAsType' => [ 'bar(); }', 'error_message' => 'MixedMethodCall', ], 'forbidLossOfInformationWhenCoercing' => [ ' * @param T::class $class */ function foo(string $class) : void {} function bar(Traversable $t) : void { foo(get_class($t)); }', 'error_message' => 'MixedArgumentTypeCoercion', ], 'bindFirstTemplatedClosureParameter' => [ ' 'InvalidScalarArgument', ], 'bindFirstTemplatedClosureParameterTypeCoercion' => [ ' 'ArgumentTypeCoercion', ], 'callableDoesNotReturnItself' => [ ' 'InvalidScalarArgument', ], 'multipleArgConstraintWithMoreRestrictiveFirstArg' => [ ' 'ArgumentTypeCoercion', ], 'multipleArgConstraintWithMoreRestrictiveSecondArg' => [ ' 'ArgumentTypeCoercion', ], 'multipleArgConstraintWithLessRestrictiveThirdArg' => [ ' 'ArgumentTypeCoercion', ], 'possiblyInvalidArgumentWithUnionFirstArg' => [ ' 'PossiblyInvalidArgument', ], 'possiblyInvalidArgumentWithUnionSecondArg' => [ ' 'PossiblyInvalidArgument', ], 'templateWithNoReturn' => [ ' 'InvalidReturnType', ], 'templateInvalidDocblockArgument' => [ ' * @psalm-suppress InvalidReturnType */ function violate($p) {}', 'error_message' => 'InvalidTemplateParam', ], 'doublyLinkedListBadParam' => [ ' */ $templated_list = new SplDoublyLinkedList(); $templated_list->add(5, []);', 'error_message' => 'InvalidArgument', ], 'copyScopedClassInFunction' => [ ' $foo */ function Foo(string $foo) : string { return $foo; }', 'error_message' => 'ReservedWord', ], 'copyScopedClassInNamespacedFunction' => [ ' $foo */ function Foo(string $foo) : string { return $foo; }', 'error_message' => 'ReservedWord', ], 'copyScopedClassInNamespacedClass' => [ ' 'ReservedWord', ], 'duplicateTemplateFunction' => [ ' */ static function of($value): self { return new self($value); } /** * @param T $value */ private function __construct($value) { $this->value = $value; } }', 'error_message' => 'InvalidDocblock', ], 'preventDogCatSnafu' => [ ' $list */ function addAnimal(Collection $list) : void { $list->add(new Cat()); } /** * @param Collection $list */ function takesDogList(Collection $list) : void { addAnimal($list); // this should be an error }', 'error_message' => 'InvalidArgument', ], 'preventCovariantParamUsage' => [ ' 'InvalidTemplateParam', ], 'templateEmptyParamCoercionChangeVariable' => [ ' */ private $data; /** @psalm-param iterable $data */ public function __construct(iterable $data = []) { $this->data = $data; } } /** @psalm-param Collection $c */ function takesStringCollection(Collection $c): void {} /** @psalm-param Collection $c */ function takesIntCollection(Collection $c): void {} $collection = new Collection(); takesStringCollection($collection); takesIntCollection($collection);', 'error_message' => 'InvalidScalarArgument', ], 'argumentExpectsFleshOutTIndexedAccess' => [ 'data = $data; } /** * @template K as key-of * * @param K $property * * @return TData[K] */ public function __get(string $property) { // validation logic would go here return $this->data[$property]; } /** * @template K as key-of * * @param K $property * @param TData[K] $value */ public function __set(string $property, $value) { // data updating would go here $this->data[$property] = $value; } } /** @extends Row */ class CharacterRow extends Row {} $mario = new CharacterRow(["id" => 5, "name" => "Mario", "height" => 3.5]); $mario->ame = "Luigi";', 'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:47:29 - Argument 1 of CharacterRow::__set expects string(id)|string(name)|string(height), string(ame) provided', ], 'constrainTemplateTypeWhenClassStringUsed' => [ ' $type * @psalm-return T */ public function getObject(string $type) { return 3; } }', 'error_message' => 'InvalidReturnStatement' ], 'preventTemplateTypeAsBeingUsedInsideFunction' => [ ' 'InvalidArgument' ], 'preventWrongTemplateBeingPassed' => [ ' 'InvalidArgument' ], 'preventTemplateTypeReturnMoreGeneral' => [ ' 'InvalidReturnStatement' ], ]; } }