2018-12-20 07:06:43 +01:00
|
|
|
<?php
|
2021-12-15 04:58:32 +01:00
|
|
|
|
2018-12-20 07:06:43 +01:00
|
|
|
namespace Psalm\Tests;
|
|
|
|
|
2021-12-04 21:55:53 +01:00
|
|
|
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
|
|
|
|
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;
|
|
|
|
|
2018-12-20 07:06:43 +01:00
|
|
|
class TypeAnnotationTest extends TestCase
|
|
|
|
{
|
2021-12-04 21:55:53 +01:00
|
|
|
use InvalidCodeAnalysisTestTrait;
|
|
|
|
use ValidCodeAnalysisTestTrait;
|
2018-12-20 07:06:43 +01:00
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function providerValidCodeParse(): iterable
|
2018-12-20 07:06:43 +01:00
|
|
|
{
|
|
|
|
return [
|
|
|
|
'typeAliasBeforeClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
2018-12-20 07:06:43 +01:00
|
|
|
/**
|
|
|
|
* @psalm-type CoolType = A|B|null
|
|
|
|
*/
|
|
|
|
|
|
|
|
class A {}
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/** @return CoolType */
|
|
|
|
function foo() {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
return new A();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
return new B();
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @param CoolType $a **/
|
|
|
|
function bar ($a) : void { }
|
|
|
|
|
2019-03-23 19:27:54 +01:00
|
|
|
bar(foo());',
|
2018-12-20 07:06:43 +01:00
|
|
|
],
|
|
|
|
'typeAliasBeforeFunction' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
2018-12-20 07:06:43 +01:00
|
|
|
/**
|
|
|
|
* @psalm-type A_OR_B = A|B
|
|
|
|
* @psalm-type CoolType = A_OR_B|null
|
|
|
|
* @return CoolType
|
|
|
|
*/
|
|
|
|
function foo() {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
return new A();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
return new B();
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
class A {}
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/** @param CoolType $a **/
|
|
|
|
function bar ($a) : void { }
|
|
|
|
|
2019-03-23 19:27:54 +01:00
|
|
|
bar(foo());',
|
2018-12-20 07:06:43 +01:00
|
|
|
],
|
|
|
|
'typeAliasInSeparateBlockBeforeFunction' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
2018-12-20 07:06:43 +01:00
|
|
|
/**
|
|
|
|
* @psalm-type CoolType = A|B|null
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
* @return CoolType
|
|
|
|
*/
|
|
|
|
function foo() {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
return new A();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
return new B();
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
class A {}
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/** @param CoolType $a **/
|
|
|
|
function bar ($a) : void { }
|
|
|
|
|
2019-03-23 19:27:54 +01:00
|
|
|
bar(foo());',
|
2018-12-20 07:06:43 +01:00
|
|
|
],
|
|
|
|
'almostFreeStandingTypeAlias' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-12-20 07:06:43 +01:00
|
|
|
/**
|
|
|
|
* @psalm-type CoolType = A|B|null
|
|
|
|
*/
|
|
|
|
|
|
|
|
// this breaks up the line
|
|
|
|
|
|
|
|
class A {}
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/** @return CoolType */
|
|
|
|
function foo() {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
return new A();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
return new B();
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @param CoolType $a **/
|
|
|
|
function bar ($a) : void { }
|
|
|
|
|
2019-03-23 19:27:54 +01:00
|
|
|
bar(foo());',
|
2018-12-20 07:06:43 +01:00
|
|
|
],
|
|
|
|
'typeAliasUsedTwice' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Baz;
|
|
|
|
|
2018-12-20 07:06:43 +01:00
|
|
|
/** @psalm-type TA = array<int, string> */
|
|
|
|
|
|
|
|
class Bar {
|
|
|
|
public function foo() : void {
|
|
|
|
$bar =
|
|
|
|
/** @return TA */
|
|
|
|
function() {
|
|
|
|
return ["hello"];
|
|
|
|
};
|
|
|
|
|
|
|
|
/** @var array<int, TA> */
|
|
|
|
$bat = [$bar(), $bar()];
|
|
|
|
|
|
|
|
foreach ($bat as $b) {
|
|
|
|
echo $b[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-11-12 02:14:21 +01:00
|
|
|
* @psalm-type _A=array{elt:int}
|
2018-12-20 07:06:43 +01:00
|
|
|
* @param _A $p
|
|
|
|
* @return _A
|
|
|
|
*/
|
|
|
|
function f($p) {
|
|
|
|
/** @var _A */
|
|
|
|
$r = $p;
|
|
|
|
return $r;
|
|
|
|
}',
|
|
|
|
],
|
2020-08-18 15:34:07 +02:00
|
|
|
'classTypeAliasSimple' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Bar;
|
|
|
|
|
2022-11-12 02:14:21 +01:00
|
|
|
/** @psalm-type PhoneType = array{phone: string} */
|
2020-05-15 22:18:52 +02:00
|
|
|
class Phone {
|
|
|
|
/** @psalm-return PhoneType */
|
|
|
|
public function toArray(): array {
|
|
|
|
return ["phone" => "Nokia"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-12 02:14:21 +01:00
|
|
|
/** @psalm-type NameType = array{name: string} */
|
2020-05-15 22:18:52 +02:00
|
|
|
class Name {
|
|
|
|
/** @psalm-return NameType */
|
|
|
|
function toArray(): array {
|
|
|
|
return ["name" => "Matt"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type PhoneType from Phone as PhoneType2
|
|
|
|
* @psalm-import-type NameType from Name as NameType2
|
|
|
|
*
|
|
|
|
* @psalm-type UserType = PhoneType2&NameType2
|
|
|
|
*/
|
|
|
|
class User {
|
|
|
|
/** @psalm-return UserType */
|
|
|
|
function toArray(): array {
|
|
|
|
return array_merge(
|
|
|
|
(new Name)->toArray(),
|
|
|
|
(new Phone)->toArray()
|
|
|
|
);
|
|
|
|
}
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2020-05-15 22:18:52 +02:00
|
|
|
],
|
2020-05-16 22:43:35 +02:00
|
|
|
'classTypeAliasImportWithAlias' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Bar;
|
|
|
|
|
2022-11-12 02:14:21 +01:00
|
|
|
/** @psalm-type PhoneType = array{phone: string} */
|
2020-05-16 22:43:35 +02:00
|
|
|
class Phone {
|
|
|
|
/** @psalm-return PhoneType */
|
|
|
|
public function toArray(): array {
|
|
|
|
return ["phone" => "Nokia"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type PhoneType from Phone as TPhone
|
|
|
|
*/
|
|
|
|
class User {
|
|
|
|
/** @psalm-return TPhone */
|
|
|
|
function toArray(): array {
|
|
|
|
return array_merge([], (new Phone)->toArray());
|
|
|
|
}
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2020-05-16 22:43:35 +02:00
|
|
|
],
|
|
|
|
'classTypeAliasDirectUsage' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Bar;
|
|
|
|
|
2022-11-12 02:14:21 +01:00
|
|
|
/** @psalm-type PhoneType = array{phone: string} */
|
2020-05-16 22:43:35 +02:00
|
|
|
class Phone {
|
|
|
|
/** @psalm-return PhoneType */
|
|
|
|
public function toArray(): array {
|
|
|
|
return ["phone" => "Nokia"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type PhoneType from Phone
|
|
|
|
*/
|
|
|
|
class User {
|
|
|
|
/** @psalm-return PhoneType */
|
|
|
|
function toArray(): array {
|
|
|
|
return array_merge([], (new Phone)->toArray());
|
|
|
|
}
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2020-05-16 22:43:35 +02:00
|
|
|
],
|
|
|
|
'classTypeAliasFromExternalNamespace' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-05-16 22:43:35 +02:00
|
|
|
namespace Foo {
|
2022-11-12 02:14:21 +01:00
|
|
|
/** @psalm-type PhoneType = array{phone: string} */
|
2020-05-16 22:43:35 +02:00
|
|
|
class Phone {
|
|
|
|
/** @psalm-return PhoneType */
|
|
|
|
public function toArray(): array {
|
|
|
|
return ["phone" => "Nokia"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace Bar {
|
|
|
|
/**
|
|
|
|
* @psalm-import-type PhoneType from \Foo\Phone
|
|
|
|
*/
|
|
|
|
class User {
|
|
|
|
/** @psalm-return PhoneType */
|
|
|
|
function toArray(): array {
|
|
|
|
return (new \Foo\Phone)->toArray();
|
|
|
|
}
|
|
|
|
}
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2020-05-16 22:43:35 +02:00
|
|
|
],
|
2020-06-07 18:00:55 +02:00
|
|
|
'importTypeForParam' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Bar;
|
|
|
|
|
2020-06-07 18:00:55 +02:00
|
|
|
/**
|
|
|
|
* @psalm-type Type = self::NULL|self::BOOL|self::INT|self::STRING
|
|
|
|
*/
|
|
|
|
interface I
|
|
|
|
{
|
|
|
|
public const NULL = 0;
|
|
|
|
public const BOOL = 1;
|
|
|
|
public const INT = 2;
|
|
|
|
public const STRING = 3;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-param Type $type
|
|
|
|
*/
|
|
|
|
public function a(int $type): void;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type Type from I as Type2
|
|
|
|
*/
|
|
|
|
abstract class C implements I
|
|
|
|
{
|
|
|
|
public function a(int $type): void
|
|
|
|
{
|
|
|
|
$this->b($type);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-param Type2 $type
|
|
|
|
*/
|
|
|
|
private function b(int $type): void
|
|
|
|
{
|
|
|
|
}
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2020-06-07 18:00:55 +02:00
|
|
|
],
|
2020-08-22 14:20:05 +02:00
|
|
|
'usedInVarForForeach' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2022-11-12 02:14:21 +01:00
|
|
|
/** @psalm-type _B=array{p1:string} */
|
2020-08-22 14:20:05 +02:00
|
|
|
function e(array $a): void
|
|
|
|
{
|
|
|
|
/** @var _B $elt */
|
|
|
|
foreach ($a as $elt) {
|
|
|
|
echo $elt["p1"];
|
|
|
|
}
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2020-08-25 16:15:56 +02:00
|
|
|
],
|
|
|
|
'objectWithPropertiesAlias' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-25 16:15:56 +02:00
|
|
|
/**
|
|
|
|
* @psalm-type FooStruct=string
|
|
|
|
*/
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type FooStruct from A as F2
|
|
|
|
*/
|
|
|
|
class B {
|
|
|
|
/**
|
|
|
|
* @param object{foo: F2} $a
|
|
|
|
* @return object{foo: string}
|
|
|
|
*/
|
|
|
|
public function bar($a) {
|
|
|
|
return $a;
|
|
|
|
}
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2020-08-25 16:15:56 +02:00
|
|
|
],
|
2021-02-12 23:02:24 +01:00
|
|
|
'sameDocBlockTypeAliasAsTypeParameterForInterface' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-12 23:02:24 +01:00
|
|
|
/** @template T */
|
|
|
|
interface A {
|
|
|
|
/** @return T */
|
|
|
|
public function output();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-type Foo=string
|
|
|
|
* @implements A<Foo>
|
|
|
|
*/
|
|
|
|
class C implements A {
|
|
|
|
public function output() {
|
|
|
|
return "hello";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$instance = new C();
|
|
|
|
$output = $instance->output();',
|
2022-01-13 19:49:37 +01:00
|
|
|
'assertions' => [
|
2021-02-12 23:02:24 +01:00
|
|
|
'$output' => 'string',
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'sameDocBlockTypeAliasAsTypeParameterForExtendedRegularClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-12 23:02:24 +01:00
|
|
|
/** @template T */
|
|
|
|
class A {
|
|
|
|
/** @var T */
|
|
|
|
public $value;
|
|
|
|
|
|
|
|
/** @param T $value */
|
|
|
|
public function __construct($value) {
|
|
|
|
$this->value = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-type Foo=string
|
|
|
|
* @extends A<Foo>
|
|
|
|
*/
|
|
|
|
class C extends A {}
|
|
|
|
|
|
|
|
$instance = new C("hello");
|
|
|
|
$output = $instance->value;',
|
2022-01-13 19:49:37 +01:00
|
|
|
'assertions' => [
|
2021-02-12 23:02:24 +01:00
|
|
|
'$output' => 'string',
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'sameDocBlockTypeAliasAsTypeParameterForExtendedAbstractClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-12 23:02:24 +01:00
|
|
|
/** @template T */
|
|
|
|
abstract class A {
|
|
|
|
/** @var T */
|
|
|
|
public $value;
|
|
|
|
|
|
|
|
/** @param T $value */
|
|
|
|
public function __construct($value) {
|
|
|
|
$this->value = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-type Foo=string
|
|
|
|
* @extends A<Foo>
|
|
|
|
*/
|
|
|
|
class C extends A {}
|
|
|
|
|
|
|
|
$instance = new C("hello");
|
|
|
|
$output = $instance->value;',
|
2022-01-13 19:49:37 +01:00
|
|
|
'assertions' => [
|
2021-02-12 23:02:24 +01:00
|
|
|
'$output' => 'string',
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'importedTypeAliasAsTypeParameterForImplementation' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-12 23:02:24 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
|
|
|
/** @template T */
|
|
|
|
interface A {}
|
|
|
|
|
|
|
|
/** @psalm-type Foo=string */
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type Foo from B
|
|
|
|
* @implements A<Foo>
|
|
|
|
*/
|
|
|
|
class C implements A {}',
|
|
|
|
],
|
2021-11-17 19:19:10 +01:00
|
|
|
'importedTypeAliasAsConstrainedTypeParameterForImplementation' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-11-17 19:19:10 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
|
|
|
/** @template T of string */
|
|
|
|
interface A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-type Foo = "foo"
|
|
|
|
*/
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type Foo from B
|
|
|
|
* @implements A<Foo>
|
|
|
|
*/
|
|
|
|
class C implements A {}
|
2022-12-18 17:15:15 +01:00
|
|
|
',
|
2021-11-17 19:19:10 +01:00
|
|
|
],
|
2021-02-12 23:02:24 +01:00
|
|
|
'importedTypeAliasAsTypeParameterForExtendedClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-12 23:02:24 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
|
|
|
/** @template T */
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/** @psalm-type Foo=string */
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type Foo from B
|
|
|
|
* @extends A<Foo>
|
|
|
|
*/
|
|
|
|
class C extends A {}',
|
|
|
|
],
|
|
|
|
'importedTypeAliasAsTypeParameterForExtendedAbstractClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-12 23:02:24 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
|
|
|
/** @template T */
|
|
|
|
abstract class A {}
|
|
|
|
|
|
|
|
/** @psalm-type Foo=string */
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type Foo from B
|
|
|
|
* @extends A<Foo>
|
|
|
|
*/
|
|
|
|
class C extends A {}',
|
|
|
|
],
|
|
|
|
'importedTypeAliasRenamedAsTypeParameterForImplementation' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-12 23:02:24 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
|
|
|
/** @template T */
|
|
|
|
interface A {}
|
|
|
|
|
|
|
|
/** @psalm-type Foo=string */
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type Foo from B as NewName
|
|
|
|
* @implements A<NewName>
|
|
|
|
*/
|
|
|
|
class C implements A {}',
|
|
|
|
],
|
|
|
|
'importedTypeAliasRenamedAsTypeParameterForExtendedClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-12 23:02:24 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
|
|
|
/** @template T */
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/** @psalm-type Foo=string */
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type Foo from B as NewName
|
|
|
|
* @extends A<NewName>
|
|
|
|
*/
|
|
|
|
class C extends A {}',
|
|
|
|
],
|
|
|
|
'importedTypeAliasRenamedAsTypeParameterForExtendedAbstractClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-12 23:02:24 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
|
|
|
/** @template T */
|
|
|
|
abstract class A {}
|
|
|
|
|
|
|
|
/** @psalm-type Foo=string */
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type Foo from B as NewName
|
|
|
|
* @extends A<NewName>
|
|
|
|
*/
|
|
|
|
class C extends A {}',
|
|
|
|
],
|
|
|
|
'importedTypeInsideLocalTypeAliasUsedAsTypeParameter' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-12 23:02:24 +01:00
|
|
|
/** @template T */
|
|
|
|
abstract class A {
|
|
|
|
/** @var T */
|
|
|
|
public $value;
|
|
|
|
|
|
|
|
/** @param T $value */
|
|
|
|
public function __construct($value) {
|
|
|
|
$this->value = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-type Foo=string
|
|
|
|
*/
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type Foo from B
|
|
|
|
* @psalm-type Baz=Foo
|
|
|
|
*
|
|
|
|
* @extends A<Baz>
|
|
|
|
*/
|
|
|
|
class C extends A {}
|
|
|
|
|
2021-04-20 04:56:08 +02:00
|
|
|
$instance = new C("hello");
|
|
|
|
$output = $instance->value;',
|
2022-01-13 19:49:37 +01:00
|
|
|
'assertions' => [
|
2021-04-20 04:56:08 +02:00
|
|
|
'$output' => 'string',
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'importedTypeWithPhpstanAnnotation' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-04-20 04:56:08 +02:00
|
|
|
/** @template T */
|
|
|
|
abstract class A {
|
|
|
|
/** @var T */
|
|
|
|
public $value;
|
|
|
|
|
|
|
|
/** @param T $value */
|
|
|
|
public function __construct($value) {
|
|
|
|
$this->value = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @phpstan-type Foo=string
|
|
|
|
*/
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @phpstan-import-type Foo from B
|
|
|
|
* @phpstan-type Baz=Foo
|
|
|
|
*
|
|
|
|
* @extends A<Baz>
|
|
|
|
*/
|
|
|
|
class C extends A {}
|
|
|
|
|
2021-02-12 23:02:24 +01:00
|
|
|
$instance = new C("hello");
|
|
|
|
$output = $instance->value;',
|
2022-01-13 19:49:37 +01:00
|
|
|
'assertions' => [
|
2021-02-12 23:02:24 +01:00
|
|
|
'$output' => 'string',
|
|
|
|
],
|
|
|
|
],
|
2022-11-14 16:03:35 +01:00
|
|
|
'importedTypeUsedInAssertion' => [
|
|
|
|
'code' => '<?php
|
2022-11-14 19:54:55 +01:00
|
|
|
/** @psalm-type Foo = string */
|
|
|
|
class A {}
|
2022-11-14 16:03:35 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-immutable
|
|
|
|
* @psalm-import-type Foo from A as FooAlias
|
|
|
|
*/
|
|
|
|
class B {
|
|
|
|
/**
|
|
|
|
* @param mixed $input
|
|
|
|
* @psalm-return FooAlias
|
|
|
|
*/
|
|
|
|
public function convertToFoo($input) {
|
|
|
|
$this->assertFoo($input);
|
|
|
|
return $input;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param mixed $value
|
|
|
|
* @psalm-assert FooAlias $value
|
|
|
|
*/
|
|
|
|
private function assertFoo($value): void {
|
|
|
|
if(!is_string($value)) {
|
|
|
|
throw new \InvalidArgumentException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$instance = new B();
|
|
|
|
$output = $instance->convertToFoo("hallo");
|
|
|
|
',
|
|
|
|
'assertions' => [
|
|
|
|
'$output' => 'string',
|
2022-12-18 17:15:15 +01:00
|
|
|
],
|
2022-11-15 14:32:17 +01:00
|
|
|
],
|
|
|
|
'importedTypeUsedInOtherType' => [
|
|
|
|
'code' => '<?php
|
|
|
|
/** @psalm-type OpeningTypes=self::TYPE_A|self::TYPE_B */
|
|
|
|
class Foo {
|
|
|
|
public const TYPE_A = 1;
|
|
|
|
public const TYPE_B = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type OpeningTypes from Foo
|
|
|
|
* @psalm-type OpeningTypeAssignment=list<OpeningTypes>
|
|
|
|
*/
|
|
|
|
class Main {
|
|
|
|
/** @return OpeningTypeAssignment */
|
|
|
|
public function doStuff(): array {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$instance = new Main();
|
|
|
|
$output = $instance->doStuff();
|
|
|
|
',
|
|
|
|
'assertions' => [
|
|
|
|
'$output===' => 'list<1|2>',
|
2022-12-18 17:15:15 +01:00
|
|
|
],
|
|
|
|
],
|
2023-03-12 18:34:41 +01:00
|
|
|
'callableWithReturnTypeTypeAliasWithinBackets' => [
|
|
|
|
'code' => '<?php
|
|
|
|
/** @psalm-type TCallback (callable():int) */
|
|
|
|
class Foo {
|
|
|
|
/** @psalm-var TCallback */
|
|
|
|
public static callable $callback;
|
|
|
|
}
|
|
|
|
$output = Foo::$callback;
|
|
|
|
',
|
|
|
|
'assertions' => [
|
|
|
|
'$output===' => 'callable():int',
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'callableWithReturnTypeTypeAlias' => [
|
|
|
|
'code' => '<?php
|
|
|
|
/** @psalm-type TCallback callable():int */
|
|
|
|
class Foo {
|
|
|
|
/** @psalm-var TCallback */
|
|
|
|
public static callable $callback;
|
|
|
|
}
|
|
|
|
$output = Foo::$callback;
|
|
|
|
',
|
|
|
|
'assertions' => [
|
|
|
|
'$output===' => 'callable():int',
|
|
|
|
],
|
|
|
|
],
|
2023-03-13 06:07:19 +01:00
|
|
|
'unionOfStringsContainingBraceChar' => [
|
2023-03-12 18:34:41 +01:00
|
|
|
'code' => '<?php
|
|
|
|
/** @psalm-type T \'{\'|\'}\' */
|
|
|
|
class Foo {
|
|
|
|
/** @psalm-var T */
|
|
|
|
public static string $t;
|
|
|
|
}
|
|
|
|
$t = Foo::$t;
|
|
|
|
',
|
|
|
|
'assertions' => [
|
|
|
|
'$t===' => '\'{\'|\'}\'',
|
|
|
|
],
|
|
|
|
],
|
2023-03-13 06:07:19 +01:00
|
|
|
'unionOfStringsContainingGTChar' => [
|
2023-03-12 18:34:41 +01:00
|
|
|
'code' => '<?php
|
|
|
|
/** @psalm-type T \'<\'|\'>\' */
|
|
|
|
class Foo {
|
|
|
|
/** @psalm-var T */
|
|
|
|
public static string $t;
|
|
|
|
}
|
|
|
|
$t = Foo::$t;
|
|
|
|
',
|
|
|
|
'assertions' => [
|
|
|
|
'$t===' => '\'<\'|\'>\'',
|
|
|
|
],
|
|
|
|
],
|
2023-03-13 06:07:19 +01:00
|
|
|
'unionOfStringsContainingBracketChar' => [
|
2023-03-12 18:34:41 +01:00
|
|
|
'code' => '<?php
|
|
|
|
/** @psalm-type T \'(\'|\')\' */
|
|
|
|
class Foo {
|
|
|
|
/** @psalm-var T */
|
|
|
|
public static string $t;
|
|
|
|
}
|
|
|
|
$t = Foo::$t;
|
|
|
|
',
|
|
|
|
'assertions' => [
|
|
|
|
'$t===' => '\'(\'|\')\'',
|
|
|
|
],
|
|
|
|
],
|
2023-03-13 06:07:19 +01:00
|
|
|
'bareWordsCommentAfterType' => [
|
2023-03-12 18:34:41 +01:00
|
|
|
'code' => '<?php
|
|
|
|
/**
|
|
|
|
* @psalm-type T string
|
|
|
|
*
|
2023-03-13 06:07:19 +01:00
|
|
|
* Lorem ipsum
|
2023-03-12 18:34:41 +01:00
|
|
|
*/
|
|
|
|
class Foo {
|
|
|
|
/** @psalm-var T */
|
|
|
|
public static string $t;
|
|
|
|
}
|
|
|
|
$t = Foo::$t;
|
|
|
|
',
|
|
|
|
'assertions' => [
|
|
|
|
'$t===' => 'string',
|
|
|
|
],
|
|
|
|
],
|
2023-03-05 22:14:09 +01:00
|
|
|
'handlesTypeWhichEndsWithRoundBracket' => [
|
|
|
|
'code' => '<?php
|
|
|
|
/**
|
|
|
|
* @psalm-type Foo=(iterable<mixed>)
|
|
|
|
*/
|
|
|
|
class A {}
|
|
|
|
',
|
|
|
|
],
|
2023-03-12 22:58:20 +01:00
|
|
|
'commentAfterType' => [
|
|
|
|
'code' => '<?php
|
|
|
|
/**
|
|
|
|
* @psalm-type TTest string
|
|
|
|
*
|
|
|
|
* This is a test class.
|
|
|
|
*/
|
|
|
|
class Test {}',
|
|
|
|
],
|
|
|
|
'multilineTypeWithComment' => [
|
|
|
|
'code' => '<?php
|
|
|
|
/**
|
|
|
|
* @psalm-type PhoneType = array{
|
|
|
|
* phone: string
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* Bar
|
|
|
|
*/
|
|
|
|
class Foo {
|
|
|
|
/** @var PhoneType */
|
|
|
|
public static $phone;
|
|
|
|
}
|
|
|
|
$output = Foo::$phone;
|
|
|
|
',
|
|
|
|
'assertions' => [
|
|
|
|
'$output===' => 'array{phone: string}',
|
|
|
|
],
|
|
|
|
],
|
2023-03-23 20:43:48 +01:00
|
|
|
'combineAliasOfArrayAndArrayCorrectly' => [
|
|
|
|
'code' => '<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-type C = array{c: string}
|
|
|
|
*/
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @template F
|
|
|
|
*/
|
|
|
|
class D {
|
|
|
|
/**
|
|
|
|
* @param F $data
|
|
|
|
*/
|
|
|
|
public function __construct(public $data) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type C from A
|
|
|
|
*/
|
|
|
|
class G {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param D<array{b: bool}>|D<C> $_doesNotWork
|
|
|
|
*/
|
|
|
|
public function doesNotWork($_doesNotWork): void {
|
|
|
|
/** @psalm-check-type-exact $_doesNotWork = D<array{b?: bool, c?: string}> */;
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2018-12-20 07:06:43 +01:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function providerInvalidCodeParse(): iterable
|
2018-12-20 07:06:43 +01:00
|
|
|
{
|
|
|
|
return [
|
|
|
|
'invalidTypeAlias' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
2018-12-20 07:06:43 +01:00
|
|
|
/**
|
|
|
|
* @psalm-type CoolType = A|B>
|
|
|
|
*/
|
|
|
|
|
|
|
|
class A {}',
|
|
|
|
'error_message' => 'InvalidDocblock',
|
|
|
|
],
|
2020-08-30 17:44:14 +02:00
|
|
|
'typeAliasInTKeyedArray' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
2018-12-20 07:06:43 +01:00
|
|
|
/**
|
|
|
|
* @psalm-type aType null|"a"|"b"|"c"|"d"
|
|
|
|
*/
|
|
|
|
|
2022-11-12 02:14:21 +01:00
|
|
|
/** @psalm-return array{0:bool,1:aType} */
|
2018-12-20 07:06:43 +01:00
|
|
|
function f(): array {
|
|
|
|
return [(bool)rand(0,1), rand(0,1) ? "z" : null];
|
|
|
|
}',
|
|
|
|
'error_message' => 'InvalidReturnStatement',
|
|
|
|
],
|
2020-05-15 22:18:52 +02:00
|
|
|
'classTypeAliasInvalidReturn' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
2022-11-12 02:14:21 +01:00
|
|
|
/** @psalm-type PhoneType = array{phone: string} */
|
2020-05-15 22:18:52 +02:00
|
|
|
class Phone {
|
|
|
|
/** @psalm-return PhoneType */
|
|
|
|
public function toArray(): array {
|
|
|
|
return ["phone" => "Nokia"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-12 02:14:21 +01:00
|
|
|
/** @psalm-type NameType = array{name: string} */
|
2020-05-15 22:18:52 +02:00
|
|
|
class Name {
|
|
|
|
/** @psalm-return NameType */
|
|
|
|
function toArray(): array {
|
|
|
|
return ["name" => "Matt"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type PhoneType from Phone as PhoneType2
|
|
|
|
* @psalm-import-type NameType from Name as NameType2
|
|
|
|
*
|
|
|
|
* @psalm-type UserType = PhoneType2&NameType2
|
|
|
|
*/
|
|
|
|
class User {
|
|
|
|
/** @psalm-return UserType */
|
|
|
|
function toArray(): array {
|
|
|
|
return array_merge(
|
|
|
|
(new Name)->toArray(),
|
|
|
|
["foo" => "bar"]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'error_message' => 'InvalidReturnStatement',
|
|
|
|
],
|
2020-08-17 04:53:53 +02:00
|
|
|
'classTypeInvalidAliasImport' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
|
|
|
class Phone {
|
|
|
|
function toArray(): array {
|
|
|
|
return ["name" => "Matt"];
|
|
|
|
}
|
2020-05-16 22:43:35 +02:00
|
|
|
}
|
|
|
|
|
2020-08-18 15:34:07 +02:00
|
|
|
/**
|
|
|
|
* @psalm-import-type PhoneType from Phone
|
|
|
|
*/
|
|
|
|
class User {}',
|
2020-08-17 04:53:53 +02:00
|
|
|
'error_message' => 'InvalidTypeImport',
|
2020-05-16 22:43:35 +02:00
|
|
|
],
|
|
|
|
'classTypeAliasFromInvalidClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type PhoneType from Phone
|
|
|
|
*/
|
|
|
|
class User {}',
|
2020-05-16 22:43:35 +02:00
|
|
|
'error_message' => 'UndefinedDocblockClass',
|
|
|
|
],
|
2020-08-17 04:53:53 +02:00
|
|
|
'malformedImportMissingFrom' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
2020-08-17 04:53:53 +02:00
|
|
|
/** @psalm-import-type Thing */
|
|
|
|
class C {}
|
|
|
|
',
|
|
|
|
'error_message' => 'InvalidTypeImport',
|
|
|
|
],
|
|
|
|
'malformedImportMissingSourceClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
2020-08-17 04:53:53 +02:00
|
|
|
/** @psalm-import-type Thing from */
|
|
|
|
class C {}
|
|
|
|
',
|
|
|
|
'error_message' => 'InvalidTypeImport',
|
|
|
|
],
|
|
|
|
'malformedImportMisspelledFrom' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
2020-08-17 04:53:53 +02:00
|
|
|
/** @psalm-import-type Thing morf */
|
|
|
|
class C {}
|
|
|
|
',
|
|
|
|
'error_message' => 'InvalidTypeImport',
|
|
|
|
],
|
|
|
|
'malformedImportMissingAlias' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
2020-08-17 04:53:53 +02:00
|
|
|
/** @psalm-import-type Thing from Somewhere as */
|
|
|
|
class C {}
|
|
|
|
',
|
|
|
|
'error_message' => 'InvalidTypeImport',
|
|
|
|
],
|
2020-08-16 18:43:46 +02:00
|
|
|
'noCrashWithPriorReference' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-18 15:34:07 +02:00
|
|
|
namespace Barrr;
|
|
|
|
|
2020-08-16 18:43:46 +02:00
|
|
|
/**
|
2022-11-12 02:14:21 +01:00
|
|
|
* @psalm-type _C=array{c:_CC}
|
2020-08-16 18:43:46 +02:00
|
|
|
* @psalm-type _CC=float
|
|
|
|
*/
|
|
|
|
class A {
|
2020-08-18 15:34:07 +02:00
|
|
|
/**
|
|
|
|
* @param _C $arr
|
|
|
|
*/
|
|
|
|
public function foo(array $arr) : void {}
|
2020-08-16 18:43:46 +02:00
|
|
|
}',
|
2020-08-18 15:34:07 +02:00
|
|
|
'error_message' => 'UndefinedDocblockClass',
|
2020-08-16 18:43:46 +02:00
|
|
|
],
|
2020-08-16 22:12:29 +02:00
|
|
|
'mergeImportedTypes' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-16 22:12:29 +02:00
|
|
|
namespace A\B;
|
|
|
|
|
|
|
|
/**
|
2022-11-12 02:14:21 +01:00
|
|
|
* @psalm-type _A=array{
|
2020-08-16 22:12:29 +02:00
|
|
|
* id:int
|
|
|
|
* }
|
|
|
|
*
|
2022-11-12 02:14:21 +01:00
|
|
|
* @psalm-type _B=array{
|
2020-08-16 22:12:29 +02:00
|
|
|
* id:int,
|
|
|
|
* something:int
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
class Types
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace A;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-import-type _A from \A\B\Types as _AA
|
|
|
|
* @psalm-import-type _B from \A\B\Types as _BB
|
|
|
|
*/
|
|
|
|
class Id
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @psalm-param _AA|_BB $_item
|
|
|
|
*/
|
|
|
|
public function ff(array $_item): int
|
|
|
|
{
|
|
|
|
return $_item["something"];
|
|
|
|
}
|
|
|
|
}',
|
2020-08-17 04:53:53 +02:00
|
|
|
'error_message' => 'PossiblyUndefinedArrayOffset',
|
2020-08-16 22:12:29 +02:00
|
|
|
],
|
2020-11-22 06:26:14 +01:00
|
|
|
'noCrashWithSelfReferencingType' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-11-22 06:26:14 +01:00
|
|
|
/**
|
2022-11-12 02:14:21 +01:00
|
|
|
* @psalm-type SomeType = array{
|
2020-11-22 06:26:14 +01:00
|
|
|
* parent?: SomeType,
|
|
|
|
* foo?: int,
|
|
|
|
* }
|
|
|
|
* @psalm-param SomeType $input
|
|
|
|
*/
|
|
|
|
function test(array $input):void {}',
|
|
|
|
'error_message' => 'InvalidDocblock',
|
|
|
|
],
|
2021-02-12 23:02:24 +01:00
|
|
|
'invalidTypeWhenNotImported' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-12 23:02:24 +01:00
|
|
|
|
|
|
|
/** @psalm-type Foo = string */
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/** @template T */
|
|
|
|
interface B {}
|
|
|
|
|
|
|
|
/** @implements B<Foo> */
|
|
|
|
class C implements B {}',
|
|
|
|
'error_message' => 'UndefinedDocblockClass',
|
|
|
|
],
|
|
|
|
'invalidTypeWhenNotImportedInsideAnotherTypeAlias' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-12 23:02:24 +01:00
|
|
|
|
|
|
|
/** @psalm-type Foo = string */
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/** @template T */
|
|
|
|
interface B {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-type Baz=Foo
|
|
|
|
* @implements B<Baz>
|
|
|
|
*/
|
|
|
|
class C implements B {}',
|
|
|
|
'error_message' => 'UndefinedDocblockClass',
|
|
|
|
],
|
2023-02-24 08:28:58 +01:00
|
|
|
'duplicateKeyInArrayShapeOnInterfaceIsReported' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-type Attributes = array{
|
|
|
|
* name: string,
|
|
|
|
* email: string,
|
|
|
|
* email: string,
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
interface A {
|
|
|
|
/**
|
|
|
|
* @return Attributes
|
|
|
|
*/
|
|
|
|
public function getAttributes(): array;
|
|
|
|
}
|
|
|
|
PHP,
|
|
|
|
'error_message' => 'InvalidDocblock',
|
|
|
|
],
|
|
|
|
'duplicateKeyInArrayShapeOnAClassIsReported' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-type Attributes = array{
|
|
|
|
* name: string,
|
|
|
|
* email: string,
|
|
|
|
* email: string,
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
class A {
|
|
|
|
/**
|
|
|
|
* @return Attributes
|
|
|
|
*/
|
|
|
|
public function getAttributes(): array {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PHP,
|
|
|
|
'error_message' => 'InvalidDocblock',
|
|
|
|
],
|
|
|
|
'duplicateKeyInArrayShapeOnATraitIsReported' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-type Attributes = array{
|
|
|
|
* name: string,
|
|
|
|
* email: string,
|
|
|
|
* email: string,
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
trait A {
|
|
|
|
/**
|
|
|
|
* @return Attributes
|
|
|
|
*/
|
|
|
|
public function getAttributes(): array {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PHP,
|
|
|
|
'error_message' => 'InvalidDocblock',
|
|
|
|
],
|
|
|
|
'duplicateKeyInArrayShapeOnAnEnumIsReported' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-type Attributes = array{
|
|
|
|
* name: string,
|
|
|
|
* email: string,
|
|
|
|
* email: string,
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
enum A {
|
|
|
|
case FOO;
|
|
|
|
}
|
|
|
|
PHP,
|
|
|
|
'error_message' => 'InvalidDocblock',
|
|
|
|
'ignored_issues' => [],
|
|
|
|
'php_version' => '8.1',
|
|
|
|
],
|
2018-12-20 07:06:43 +01:00
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|