mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
f824cc380a
* Add test case scenario for @psalm-import-type Signed-off-by: Jefersson Nathan <malukenho.dev@gmail.com> * Add fix for @psalm-import-type test Signed-off-by: Jefersson Nathan <malukenho.dev@gmail.com> * Add test for import ocross namespaces Signed-off-by: Jefersson Nathan <malukenho.dev@gmail.com> * Add tests for failing cases Signed-off-by: Jefersson Nathan <malukenho.dev@gmail.com>
356 lines
12 KiB
PHP
356 lines
12 KiB
PHP
<?php
|
|
namespace Psalm\Tests;
|
|
|
|
class TypeAnnotationTest extends TestCase
|
|
{
|
|
use Traits\InvalidCodeAnalysisTestTrait;
|
|
use Traits\ValidCodeAnalysisTestTrait;
|
|
|
|
/**
|
|
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
|
|
*/
|
|
public function providerValidCodeParse()
|
|
{
|
|
return [
|
|
'typeAliasBeforeClass' => [
|
|
'<?php
|
|
/**
|
|
* @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 { }
|
|
|
|
bar(foo());',
|
|
],
|
|
'typeAliasBeforeFunction' => [
|
|
'<?php
|
|
/**
|
|
* @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 { }
|
|
|
|
bar(foo());',
|
|
],
|
|
'typeAliasInSeparateBlockBeforeFunction' => [
|
|
'<?php
|
|
/**
|
|
* @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 { }
|
|
|
|
bar(foo());',
|
|
],
|
|
'almostFreeStandingTypeAlias' => [
|
|
'<?php
|
|
/**
|
|
* @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 { }
|
|
|
|
bar(foo());',
|
|
],
|
|
'typeAliasUsedTwice' => [
|
|
'<?php
|
|
/** @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];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @psalm-type _A=array{elt:int}
|
|
* @param _A $p
|
|
* @return _A
|
|
*/
|
|
function f($p) {
|
|
/** @var _A */
|
|
$r = $p;
|
|
return $r;
|
|
}',
|
|
],
|
|
'classTypeAlias' => [
|
|
'<?php
|
|
/** @psalm-type PhoneType = array{phone: string} */
|
|
class Phone {
|
|
/** @psalm-return PhoneType */
|
|
public function toArray(): array {
|
|
return ["phone" => "Nokia"];
|
|
}
|
|
}
|
|
|
|
/** @psalm-type NameType = array{name: string} */
|
|
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()
|
|
);
|
|
}
|
|
}'
|
|
],
|
|
'classTypeAliasImportWithAlias' => [
|
|
'<?php
|
|
/** @psalm-type PhoneType = array{phone: string} */
|
|
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());
|
|
}
|
|
}'
|
|
],
|
|
'classTypeAliasDirectUsage' => [
|
|
'<?php
|
|
/** @psalm-type PhoneType = array{phone: string} */
|
|
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());
|
|
}
|
|
}'
|
|
],
|
|
'classTypeAliasFromExternalNamespace' => [
|
|
'<?php
|
|
namespace Foo {
|
|
/** @psalm-type PhoneType = array{phone: string} */
|
|
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();
|
|
}
|
|
}
|
|
}'
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
|
|
*/
|
|
public function providerInvalidCodeParse()
|
|
{
|
|
return [
|
|
'invalidTypeAlias' => [
|
|
'<?php
|
|
/**
|
|
* @psalm-type CoolType = A|B>
|
|
*/
|
|
|
|
class A {}',
|
|
'error_message' => 'InvalidDocblock',
|
|
],
|
|
'typeAliasInObjectLike' => [
|
|
'<?php
|
|
/**
|
|
* @psalm-type aType null|"a"|"b"|"c"|"d"
|
|
*/
|
|
|
|
/** @psalm-return array{0:bool,1:aType} */
|
|
function f(): array {
|
|
return [(bool)rand(0,1), rand(0,1) ? "z" : null];
|
|
}',
|
|
'error_message' => 'InvalidReturnStatement',
|
|
],
|
|
'classTypeAliasInvalidReturn' => [
|
|
'<?php
|
|
/** @psalm-type PhoneType = array{phone: string} */
|
|
class Phone {
|
|
/** @psalm-return PhoneType */
|
|
public function toArray(): array {
|
|
return ["phone" => "Nokia"];
|
|
}
|
|
}
|
|
|
|
/** @psalm-type NameType = array{name: string} */
|
|
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',
|
|
],
|
|
'classTypeInvalidAlias' => [
|
|
'<?php
|
|
class Phone {
|
|
function toArray(): array {
|
|
return ["name" => "Matt"];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @psalm-import-type PhoneType from Phone
|
|
*/
|
|
class User {
|
|
/** @psalm-return UserType */
|
|
function toArray(): array {
|
|
return (new Phone)->toArray();
|
|
}
|
|
}',
|
|
'error_message' => 'UndefinedDocblockClass',
|
|
],
|
|
'classTypeAliasFromInvalidClass' => [
|
|
'<?php
|
|
/**
|
|
* @psalm-import-type PhoneType from Phone
|
|
*/
|
|
class User {
|
|
/** @psalm-return UserType */
|
|
function toArray(): array {
|
|
return [];
|
|
}
|
|
}',
|
|
'error_message' => 'UndefinedDocblockClass',
|
|
],
|
|
];
|
|
}
|
|
}
|