[ 'code' => 'getString(); $b = $child::getInt();', 'assertions' => [ '$a' => 'string', '$b' => 'int', ], ], 'anotherSimpleExample' => [ 'code' => 'b = new B(); } public function c(string $s) : void {} /** * @param array $arguments * @return mixed */ public function __call(string $method, array $arguments) { return $this->b->$method(...$arguments); } } class B { public function b(): void { echo "b"; } public function c(int $s) : void {} } $a = new A(); $a->b();' ], 'allowConstructor' => [ 'code' => 'i = 1; } } class M { public function __construct() {} } /** * @mixin M */ class A extends AParent {}' ], 'implicitMixin' => [ 'code' => 'valid()) { if (!$iterator->isDot() && $iterator->isLink()) {} $iterator->next(); } }' ], 'wrapCustomIterator' => [ 'code' => ' */ class Subject implements Iterator { /** * the index method exists * * @param int $index * @return bool */ public function index($index) { return true; } public function current() { return 2; } public function next() {} public function key() { return 1; } public function valid() { return false; } public function rewind() {} } $iter = new IteratorIterator(new Subject()); $b = $iter->index(0);', 'assertions' => [ '$b' => 'bool', ] ], 'templatedMixin' => [ 'code' => ' */ class Bar {} $bar = new Bar(); $b = $bar->hi();', 'assertions' => [ '$b' => 'string', ] ], 'templatedMixinSelf' => [ 'code' => 'item = $item; } /** * @return T */ public function get() { return $this->item; } } /** * @mixin Animal */ class Dog { public function __construct() {} } function getDog(): Dog { return (new Dog())->get(); }' ], 'inheritPropertyAnnotations' => [ 'code' => 'foo; }' ], 'inheritTemplatedMixinWithStatic' => [ 'code' => 'var = $var; } /** * @psalm-return T */ public function type() { return $this->var; } } /** * @template T as object * @mixin Mixin * @psalm-consistent-constructor */ abstract class Foo { /** @var Mixin */ public object $obj; public function __call(string $name, array $args) { return $this->obj->$name(...$args); } public function __callStatic(string $name, array $args) { return (new static)->obj->$name(...$args); } } /** * @extends Foo */ abstract class FooChild extends Foo{} /** * @psalm-suppress MissingConstructor * @psalm-suppress PropertyNotSetInConstructor */ final class FooGrandChild extends FooChild {} function test2() : FooGrandChild { return FooGrandChild::type(); } function test() : FooGrandChild { return (new FooGrandChild)->type(); }' ], 'inheritTemplatedMixinWithStaticAndFinalClass' => [ 'code' => 'var = $var; } /** * @psalm-return self */ public function getMixin() { return $this; } } /** * @template T as object * @mixin Mixin */ abstract class Foo { /** @var Mixin */ public object $obj; public function __call(string $name, array $args) { return $this->obj->$name(...$args); } } /** * @extends Foo */ abstract class FooChild extends Foo{} /** * @psalm-suppress MissingConstructor */ final class FooGrandChild extends FooChild {} /** * @psalm-return Mixin */ function test() : Mixin { return (new FooGrandChild)->getMixin(); }' ], 'mixinParseWithTextAfter' => [ 'code' => ' [ 'code' => 't = $t; } public function __call(string $method, array $parameters) { /** @psalm-suppress MixedMethodCall */ return $this->t->$method($parameters); } } /** * @method self active() */ class Model { /** * @return Builder */ public function query(): Builder { return new Builder($this); } public function __call(string $method, array $parameters) { if ($method === "active") { return new Model(); } } } /** @param Builder $b */ function foo(Builder $b) : Model { return $b->active(); }' ], 'multipleMixins' => [ 'code' => 'a(); $b = $test->b();', 'assertions' => [ '$a' => 'string', '$b' => 'int', ], ], 'inheritMultipleTemplatedMixinsWithStatic' => [ 'code' => 'var = $var; } /** * @psalm-return T */ public function type() { return $this->var; } } /** * @template T */ class OtherMixin { /** * @psalm-var T */ private $var; /** * @psalm-param T $var */ public function __construct ($var) { $this->var = $var; } /** * @psalm-return T */ public function other() { return $this->var; } } /** * @template T as object * @template T2 as string * @mixin Mixin * @mixin OtherMixin * @psalm-consistent-constructor */ abstract class Foo { /** @var Mixin */ public object $obj; /** @var OtherMixin */ public object $otherObj; public function __call(string $name, array $args) { if ($name === "test") { return $this->obj->$name(...$args); } return $this->otherObj->$name(...$args); } public function __callStatic(string $name, array $args) { if ($name === "test") { return (new static)->obj->$name(...$args); } return (new static)->otherObj->$name(...$args); } } /** * @extends Foo */ abstract class FooChild extends Foo{} /** * @psalm-suppress MissingConstructor * @psalm-suppress PropertyNotSetInConstructor */ final class FooGrandChild extends FooChild {} function test() : FooGrandChild { return FooGrandChild::type(); } function testStatic() : FooGrandChild { return (new FooGrandChild)->type(); } function other() : string { return FooGrandChild::other(); } function otherStatic() : string { return (new FooGrandChild)->other(); }' ], 'multipleMixinsWithSameMethod' => [ 'code' => 'foo();' ], 'templatedMixinBindStatic' => [ 'code' => ' */ public function getInner() { return []; } } /** * @mixin QueryBuilder */ abstract class Model {} class FooModel extends Model {} $f = new FooModel(); $g = $f->getInner();', 'assertions' => [ '$g' => 'list', ] ], ]; } public function providerInvalidCodeParse(): iterable { return [ 'undefinedMixinClass' => [ 'code' => ' 'UndefinedDocblockClass' ], 'undefinedMixinClassWithPropertyFetch' => [ 'code' => 'foo;', 'error_message' => 'UndefinedPropertyFetch' ], 'undefinedMixinClassWithPropertyAssignment' => [ 'code' => 'foo = "bar";', 'error_message' => 'UndefinedPropertyAssignment' ], 'undefinedMixinClassWithMethodCall' => [ 'code' => 'foo();', 'error_message' => 'UndefinedMethod' ], 'inheritTemplatedMixinWithSelf' => [ 'code' => 'var = $var; } /** * @psalm-return T */ public function type() { return $this->var; } } /** * @template T as object * @mixin Mixin */ abstract class Foo { /** @var Mixin */ public object $obj; public function __call(string $name, array $args) { return $this->obj->$name(...$args); } } /** * @extends Foo */ abstract class FooChild extends Foo{} /** * @psalm-suppress MissingConstructor */ final class FooGrandChild extends FooChild {} function test() : FooGrandChild { return (new FooGrandChild)->type(); }', 'error_message' => 'LessSpecificReturnStatement' ], 'mixinStaticCallShouldNotPolluteContext' => [ 'code' => ' */ class Bar { public function baz(): self { self::foobar(); return $__tmp_mixin_var__; } }', 'error_message' => 'UndefinedVariable' ], ]; } }