$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', ] ], ]; } /** * @return iterable
*/ 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', ], ]; } }