2020-01-03 05:50:19 +01:00
|
|
|
<?php
|
2021-12-15 04:58:32 +01:00
|
|
|
|
2020-01-03 05:50:19 +01:00
|
|
|
namespace Psalm\Tests;
|
|
|
|
|
2021-12-04 21:55:53 +01:00
|
|
|
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
|
|
|
|
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;
|
|
|
|
|
2020-01-03 05:50:19 +01:00
|
|
|
class MixinAnnotationTest extends TestCase
|
|
|
|
{
|
2021-12-04 21:55:53 +01:00
|
|
|
use ValidCodeAnalysisTestTrait;
|
|
|
|
use InvalidCodeAnalysisTestTrait;
|
2020-01-03 05:50:19 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
|
|
|
|
*/
|
2020-09-12 17:24:05 +02:00
|
|
|
public function providerValidCodeParse(): iterable
|
2020-01-03 05:50:19 +01:00
|
|
|
{
|
|
|
|
return [
|
|
|
|
'validSimpleAnnotations' => [
|
|
|
|
'<?php
|
|
|
|
class ParentClass {
|
|
|
|
public function __call(string $name, array $args) {}
|
2020-06-10 00:39:52 +02:00
|
|
|
public static function __callStatic(string $name, array $args) {}
|
2020-01-03 05:50:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class Provider {
|
|
|
|
public function getString() : string {
|
|
|
|
return "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setInteger(int $i) : void {}
|
2020-06-10 00:39:52 +02:00
|
|
|
|
|
|
|
public static function getInt() : int {
|
|
|
|
return 5;
|
|
|
|
}
|
2020-01-03 05:50:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @mixin Provider */
|
|
|
|
class Child extends ParentClass {}
|
|
|
|
|
|
|
|
$child = new Child();
|
|
|
|
|
|
|
|
$a = $child->getString();
|
2020-06-10 00:39:52 +02:00
|
|
|
$b = $child::getInt();',
|
2020-01-03 05:50:19 +01:00
|
|
|
'assertions' => [
|
|
|
|
'$a' => 'string',
|
2020-06-10 00:39:52 +02:00
|
|
|
'$b' => 'int',
|
2020-01-03 05:50:19 +01:00
|
|
|
],
|
|
|
|
],
|
|
|
|
'anotherSimpleExample' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @mixin B
|
|
|
|
*/
|
|
|
|
class A {
|
|
|
|
/** @var B */
|
|
|
|
private $b;
|
|
|
|
|
|
|
|
public function __construct() {
|
|
|
|
$this->b = new B();
|
|
|
|
}
|
|
|
|
|
2020-01-07 00:04:21 +01:00
|
|
|
public function c(string $s) : void {}
|
|
|
|
|
2020-01-03 05:50:19 +01:00
|
|
|
/**
|
|
|
|
* @param array<mixed> $arguments
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function __call(string $method, array $arguments)
|
|
|
|
{
|
|
|
|
return $this->b->$method(...$arguments);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class B {
|
|
|
|
public function b(): void {
|
|
|
|
echo "b";
|
|
|
|
}
|
2020-01-07 00:04:21 +01:00
|
|
|
|
|
|
|
public function c(int $s) : void {}
|
2020-01-03 05:50:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$a = new A();
|
|
|
|
$a->b();'
|
|
|
|
],
|
2020-03-19 02:38:53 +01:00
|
|
|
'allowConstructor' => [
|
|
|
|
'<?php
|
|
|
|
abstract class AParent {
|
|
|
|
protected int $i;
|
|
|
|
|
|
|
|
public function __construct() {
|
|
|
|
$this->i = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class M {
|
|
|
|
public function __construct() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @mixin M
|
|
|
|
*/
|
|
|
|
class A extends AParent {}'
|
|
|
|
],
|
2020-04-26 22:49:52 +02:00
|
|
|
'implicitMixin' => [
|
|
|
|
'<?php
|
|
|
|
function foo(string $dir) : void {
|
|
|
|
$iterator = new \RecursiveIteratorIterator(
|
|
|
|
new \RecursiveDirectoryIterator($dir)
|
|
|
|
);
|
|
|
|
|
|
|
|
while ($iterator->valid()) {
|
|
|
|
if (!$iterator->isDot() && $iterator->isLink()) {}
|
|
|
|
|
|
|
|
$iterator->next();
|
|
|
|
}
|
|
|
|
}'
|
|
|
|
],
|
2020-04-26 22:59:03 +02:00
|
|
|
'wrapCustomIterator' => [
|
|
|
|
'<?php
|
|
|
|
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);',
|
|
|
|
[
|
|
|
|
'$b' => 'bool',
|
|
|
|
]
|
|
|
|
],
|
2020-04-27 15:10:24 +02:00
|
|
|
'templatedMixin' => [
|
|
|
|
'<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @template T
|
|
|
|
*/
|
|
|
|
abstract class Foo {
|
|
|
|
/** @return T */
|
|
|
|
abstract public function hi();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @mixin Foo<string>
|
|
|
|
*/
|
|
|
|
class Bar {}
|
|
|
|
|
|
|
|
$bar = new Bar();
|
|
|
|
$b = $bar->hi();',
|
|
|
|
[
|
|
|
|
'$b' => 'string',
|
|
|
|
]
|
|
|
|
],
|
2020-05-01 17:23:02 +02:00
|
|
|
'templatedMixinSelf' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @template T
|
|
|
|
*/
|
|
|
|
class Animal {
|
|
|
|
/** @var T */
|
|
|
|
private $item;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param T $item
|
|
|
|
*/
|
|
|
|
public function __construct($item) {
|
|
|
|
$this->item = $item;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return T
|
|
|
|
*/
|
|
|
|
public function get() {
|
|
|
|
return $this->item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @mixin Animal<self>
|
|
|
|
*/
|
|
|
|
class Dog {
|
2020-05-03 01:57:38 +02:00
|
|
|
public function __construct() {}
|
2020-05-01 17:23:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function getDog(): Dog {
|
|
|
|
return (new Dog())->get();
|
|
|
|
}'
|
|
|
|
],
|
2020-05-03 01:57:38 +02:00
|
|
|
'inheritPropertyAnnotations' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @property string $foo
|
|
|
|
*/
|
2020-05-10 15:04:30 +02:00
|
|
|
class A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @mixin A
|
|
|
|
*/
|
|
|
|
class B {
|
2020-05-03 01:57:38 +02:00
|
|
|
/** @return mixed */
|
|
|
|
public function __get(string $s) {
|
|
|
|
return 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function toArray(B $b) : string {
|
|
|
|
return $b->foo;
|
|
|
|
}'
|
|
|
|
],
|
2020-05-27 17:12:09 +02:00
|
|
|
'inheritTemplatedMixinWithStatic' => [
|
2020-05-27 05:32:01 +02:00
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @template T
|
|
|
|
*/
|
|
|
|
class Mixin {
|
|
|
|
/**
|
|
|
|
* @psalm-var T
|
|
|
|
*/
|
|
|
|
private $var;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-param T $var
|
|
|
|
*/
|
|
|
|
public function __construct ($var) {
|
|
|
|
$this->var = $var;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-return T
|
|
|
|
*/
|
|
|
|
public function type() {
|
|
|
|
return $this->var;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @template T as object
|
|
|
|
* @mixin Mixin<T>
|
2020-08-06 01:39:27 +02:00
|
|
|
* @psalm-consistent-constructor
|
2020-05-27 05:32:01 +02:00
|
|
|
*/
|
|
|
|
abstract class Foo {
|
|
|
|
/** @var Mixin<T> */
|
|
|
|
public object $obj;
|
|
|
|
|
|
|
|
public function __call(string $name, array $args) {
|
|
|
|
return $this->obj->$name(...$args);
|
|
|
|
}
|
2020-06-21 00:05:35 +02:00
|
|
|
|
|
|
|
public function __callStatic(string $name, array $args) {
|
|
|
|
return (new static)->obj->$name(...$args);
|
|
|
|
}
|
2020-05-27 05:32:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @extends Foo<static>
|
|
|
|
*/
|
|
|
|
abstract class FooChild extends Foo{}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-suppress MissingConstructor
|
2021-03-29 05:10:38 +02:00
|
|
|
* @psalm-suppress PropertyNotSetInConstructor
|
2020-05-27 05:32:01 +02:00
|
|
|
*/
|
|
|
|
final class FooGrandChild extends FooChild {}
|
|
|
|
|
2020-06-21 00:05:35 +02:00
|
|
|
function test2() : FooGrandChild {
|
|
|
|
return FooGrandChild::type();
|
|
|
|
}
|
|
|
|
|
2020-05-27 05:32:01 +02:00
|
|
|
function test() : FooGrandChild {
|
|
|
|
return (new FooGrandChild)->type();
|
|
|
|
}'
|
|
|
|
],
|
2020-05-28 07:59:24 +02:00
|
|
|
'inheritTemplatedMixinWithStaticAndFinalClass' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @template T
|
|
|
|
*/
|
|
|
|
class Mixin {
|
|
|
|
/**
|
|
|
|
* @psalm-var T
|
|
|
|
*/
|
|
|
|
private $var;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-param T $var
|
|
|
|
*/
|
|
|
|
public function __construct ($var) {
|
|
|
|
$this->var = $var;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-return self<T>
|
|
|
|
*/
|
|
|
|
public function getMixin() {
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @template T as object
|
|
|
|
* @mixin Mixin<T>
|
|
|
|
*/
|
|
|
|
abstract class Foo {
|
|
|
|
/** @var Mixin<T> */
|
|
|
|
public object $obj;
|
|
|
|
|
|
|
|
public function __call(string $name, array $args) {
|
|
|
|
return $this->obj->$name(...$args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @extends Foo<static>
|
|
|
|
*/
|
|
|
|
abstract class FooChild extends Foo{}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-suppress MissingConstructor
|
|
|
|
*/
|
|
|
|
final class FooGrandChild extends FooChild {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-return Mixin<FooGrandChild>
|
|
|
|
*/
|
|
|
|
function test() : Mixin {
|
|
|
|
return (new FooGrandChild)->getMixin();
|
|
|
|
}'
|
|
|
|
],
|
2020-06-07 00:15:24 +02:00
|
|
|
'mixinParseWithTextAfter' => [
|
|
|
|
'<?php
|
|
|
|
class M {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @mixin M
|
|
|
|
* Hello
|
|
|
|
*/
|
|
|
|
class C {}'
|
|
|
|
],
|
2020-06-07 05:28:32 +02:00
|
|
|
'templatedMixinWithTemplateWithStatic' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @template T as object
|
|
|
|
* @mixin T
|
|
|
|
*/
|
|
|
|
class Builder {
|
|
|
|
private $t;
|
|
|
|
|
|
|
|
/** @param T $t */
|
|
|
|
public function __construct(object $t) {
|
|
|
|
$this->t = $t;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function __call(string $method, array $parameters) {
|
|
|
|
/** @psalm-suppress MixedMethodCall */
|
|
|
|
return $this->t->$method($parameters);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @method self active()
|
|
|
|
*/
|
|
|
|
class Model {
|
|
|
|
/**
|
|
|
|
* @return Builder<static>
|
|
|
|
*/
|
|
|
|
public function query(): Builder {
|
|
|
|
return new Builder($this);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function __call(string $method, array $parameters) {
|
|
|
|
if ($method === "active") {
|
|
|
|
return new Model();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @param Builder<Model> $b */
|
|
|
|
function foo(Builder $b) : Model {
|
|
|
|
return $b->active();
|
|
|
|
}'
|
|
|
|
],
|
2020-08-05 21:49:19 +02:00
|
|
|
'multipleMixins' => [
|
2020-08-06 01:39:27 +02:00
|
|
|
'<?php
|
2020-08-05 21:49:19 +02:00
|
|
|
class MixinA {
|
|
|
|
function a(): string { return "foo"; }
|
|
|
|
}
|
2020-08-06 01:39:27 +02:00
|
|
|
|
2020-08-05 21:49:19 +02:00
|
|
|
class MixinB {
|
|
|
|
function b(): int { return 0; }
|
|
|
|
}
|
2020-08-06 01:39:27 +02:00
|
|
|
|
2020-08-05 21:49:19 +02:00
|
|
|
/**
|
|
|
|
* @mixin MixinA
|
|
|
|
* @mixin MixinB
|
|
|
|
*/
|
|
|
|
class Test {}
|
2020-08-06 01:39:27 +02:00
|
|
|
|
2020-08-05 21:49:19 +02:00
|
|
|
$test = new Test();
|
2020-08-06 01:39:27 +02:00
|
|
|
|
2020-08-05 21:49:19 +02:00
|
|
|
$a = $test->a();
|
|
|
|
$b = $test->b();',
|
|
|
|
'assertions' => [
|
|
|
|
'$a' => 'string',
|
|
|
|
'$b' => 'int',
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'inheritMultipleTemplatedMixinsWithStatic' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @template T
|
|
|
|
*/
|
|
|
|
class Mixin {
|
|
|
|
/**
|
|
|
|
* @psalm-var T
|
|
|
|
*/
|
|
|
|
private $var;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-param T $var
|
|
|
|
*/
|
|
|
|
public function __construct ($var) {
|
|
|
|
$this->var = $var;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-return T
|
|
|
|
*/
|
|
|
|
public function type() {
|
|
|
|
return $this->var;
|
|
|
|
}
|
|
|
|
}
|
2020-08-06 01:39:27 +02:00
|
|
|
|
2020-08-05 21:49:19 +02:00
|
|
|
/**
|
|
|
|
* @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<T>
|
|
|
|
* @mixin OtherMixin<T2>
|
2020-08-06 01:39:27 +02:00
|
|
|
* @psalm-consistent-constructor
|
2020-08-05 21:49:19 +02:00
|
|
|
*/
|
|
|
|
abstract class Foo {
|
|
|
|
/** @var Mixin<T> */
|
|
|
|
public object $obj;
|
2020-08-06 01:39:27 +02:00
|
|
|
|
2020-08-05 21:49:19 +02:00
|
|
|
/** @var OtherMixin<T2> */
|
|
|
|
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<static, string>
|
|
|
|
*/
|
|
|
|
abstract class FooChild extends Foo{}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-suppress MissingConstructor
|
2021-03-29 05:10:38 +02:00
|
|
|
* @psalm-suppress PropertyNotSetInConstructor
|
2020-08-05 21:49:19 +02:00
|
|
|
*/
|
|
|
|
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();
|
|
|
|
}'
|
|
|
|
],
|
2020-08-18 14:38:30 +02:00
|
|
|
'multipleMixinsWithSameMethod' => [
|
|
|
|
'<?php
|
|
|
|
|
|
|
|
class Mix1
|
|
|
|
{
|
|
|
|
public function foo(): string
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Mix2
|
|
|
|
{
|
|
|
|
public function foo(): string
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @mixin Mix1
|
|
|
|
* @mixin Mix2
|
|
|
|
*/
|
|
|
|
class Bar
|
|
|
|
{
|
2020-12-15 04:51:32 +01:00
|
|
|
|
2020-08-18 14:38:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$bar = new Bar();
|
|
|
|
|
|
|
|
$bar->foo();'
|
|
|
|
],
|
2020-12-15 04:51:32 +01:00
|
|
|
'templatedMixinBindStatic' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @template-covariant TModel of Model
|
|
|
|
*/
|
|
|
|
class QueryBuilder {
|
|
|
|
/**
|
|
|
|
* @return list<TModel>
|
|
|
|
*/
|
|
|
|
public function getInner() {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @mixin QueryBuilder<static>
|
|
|
|
*/
|
|
|
|
abstract class Model {}
|
|
|
|
|
|
|
|
class FooModel extends Model {}
|
|
|
|
|
|
|
|
$f = new FooModel();
|
|
|
|
$g = $f->getInner();',
|
|
|
|
[
|
|
|
|
'$g' => 'list<FooModel>',
|
|
|
|
]
|
|
|
|
],
|
2020-01-03 05:50:19 +01:00
|
|
|
];
|
|
|
|
}
|
2020-05-25 06:19:46 +02:00
|
|
|
|
|
|
|
/**
|
2021-03-20 02:44:44 +01:00
|
|
|
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
|
2020-05-25 06:19:46 +02:00
|
|
|
*/
|
2020-09-12 17:24:05 +02:00
|
|
|
public function providerInvalidCodeParse(): iterable
|
2020-05-25 06:19:46 +02:00
|
|
|
{
|
|
|
|
return [
|
|
|
|
'undefinedMixinClass' => [
|
|
|
|
'<?php
|
|
|
|
/** @mixin B */
|
|
|
|
class A {}',
|
|
|
|
'error_message' => 'UndefinedDocblockClass'
|
|
|
|
],
|
|
|
|
'undefinedMixinClassWithPropertyFetch' => [
|
|
|
|
'<?php
|
|
|
|
/** @mixin B */
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
(new A)->foo;',
|
|
|
|
'error_message' => 'UndefinedPropertyFetch'
|
|
|
|
],
|
|
|
|
'undefinedMixinClassWithPropertyAssignment' => [
|
|
|
|
'<?php
|
|
|
|
/** @mixin B */
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
(new A)->foo = "bar";',
|
|
|
|
'error_message' => 'UndefinedPropertyAssignment'
|
|
|
|
],
|
|
|
|
'undefinedMixinClassWithMethodCall' => [
|
|
|
|
'<?php
|
|
|
|
/** @mixin B */
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
(new A)->foo();',
|
|
|
|
'error_message' => 'UndefinedMethod'
|
|
|
|
],
|
2020-05-27 17:12:09 +02:00
|
|
|
'inheritTemplatedMixinWithSelf' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @template T
|
|
|
|
*/
|
|
|
|
class Mixin {
|
|
|
|
/**
|
|
|
|
* @psalm-var T
|
|
|
|
*/
|
|
|
|
private $var;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-param T $var
|
|
|
|
*/
|
|
|
|
public function __construct ($var) {
|
|
|
|
$this->var = $var;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-return T
|
|
|
|
*/
|
|
|
|
public function type() {
|
|
|
|
return $this->var;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @template T as object
|
|
|
|
* @mixin Mixin<T>
|
|
|
|
*/
|
|
|
|
abstract class Foo {
|
|
|
|
/** @var Mixin<T> */
|
|
|
|
public object $obj;
|
|
|
|
|
|
|
|
public function __call(string $name, array $args) {
|
|
|
|
return $this->obj->$name(...$args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @extends Foo<self>
|
|
|
|
*/
|
|
|
|
abstract class FooChild extends Foo{}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-suppress MissingConstructor
|
|
|
|
*/
|
|
|
|
final class FooGrandChild extends FooChild {}
|
|
|
|
|
|
|
|
function test() : FooGrandChild {
|
|
|
|
return (new FooGrandChild)->type();
|
|
|
|
}',
|
|
|
|
'error_message' => 'LessSpecificReturnStatement'
|
|
|
|
],
|
2020-05-25 06:19:46 +02:00
|
|
|
];
|
|
|
|
}
|
2020-01-03 05:50:19 +01:00
|
|
|
}
|