2016-09-09 22:38:32 +02:00
|
|
|
<?php
|
|
|
|
namespace Psalm\Tests;
|
|
|
|
|
2019-01-27 23:16:22 +01:00
|
|
|
use Psalm\Internal\Codebase\Reflection;
|
2016-11-02 07:29:00 +01:00
|
|
|
use Psalm\Type;
|
2016-09-09 22:38:32 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
class TypeParseTest extends TestCase
|
2016-09-09 22:38:32 +02:00
|
|
|
{
|
2017-12-19 04:07:52 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function setUp()
|
|
|
|
{
|
2018-04-20 21:22:48 +02:00
|
|
|
//pae::setUp();
|
2017-12-19 04:07:52 +01:00
|
|
|
}
|
|
|
|
|
2018-03-22 22:55:36 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testThisToStatic()
|
|
|
|
{
|
|
|
|
$this->assertSame('static', (string) Type::parseString('$this'));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testThisToStaticUnion()
|
|
|
|
{
|
|
|
|
$this->assertSame('static|A', (string) Type::parseString('$this|A'));
|
|
|
|
}
|
|
|
|
|
2017-01-13 20:07:23 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-09-09 22:38:32 +02:00
|
|
|
public function testIntOrString()
|
|
|
|
{
|
2017-05-27 02:05:57 +02:00
|
|
|
$this->assertSame('int|string', (string) Type::parseString('int|string'));
|
2016-09-22 06:31:07 +02:00
|
|
|
}
|
2016-09-09 22:38:32 +02:00
|
|
|
|
2018-03-23 03:28:06 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBracketedIntOrString()
|
|
|
|
{
|
|
|
|
$this->assertSame('int|string', (string) Type::parseString('(int|string)'));
|
|
|
|
}
|
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBoolOrIntOrString()
|
|
|
|
{
|
|
|
|
$this->assertSame('bool|int|string', (string) Type::parseString('bool|int|string'));
|
|
|
|
}
|
|
|
|
|
2017-11-20 06:32:40 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testNullable()
|
|
|
|
{
|
|
|
|
$this->assertSame('null|string', (string) Type::parseString('?string'));
|
|
|
|
}
|
|
|
|
|
2018-08-29 22:03:16 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testNullableUnion()
|
|
|
|
{
|
|
|
|
$this->assertSame('string|int|null', (string) Type::parseString('?(string|int)'));
|
|
|
|
}
|
|
|
|
|
2018-05-21 18:55:44 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testNullableFullyQualified()
|
|
|
|
{
|
|
|
|
$this->assertSame('null|stdClass', (string) Type::parseString('?\\stdClass'));
|
|
|
|
}
|
|
|
|
|
2018-06-09 05:54:07 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testNullableOrNullable()
|
|
|
|
{
|
|
|
|
$this->assertSame('string|int|null', (string) Type::parseString('?string|?int'));
|
|
|
|
}
|
|
|
|
|
2017-01-13 20:07:23 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-09-22 06:31:07 +02:00
|
|
|
public function testArray()
|
|
|
|
{
|
2017-05-27 02:05:57 +02:00
|
|
|
$this->assertSame('array<int, int>', (string) Type::parseString('array<int, int>'));
|
|
|
|
$this->assertSame('array<int, string>', (string) Type::parseString('array<int, string>'));
|
|
|
|
$this->assertSame('array<int, static>', (string) Type::parseString('array<int, static>'));
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testArrayWithSingleArg()
|
|
|
|
{
|
2019-01-05 06:15:53 +01:00
|
|
|
$this->assertSame('array<array-key, int>', (string) Type::parseString('array<int>'));
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testArrayWithNestedSingleArg()
|
|
|
|
{
|
2019-01-05 06:15:53 +01:00
|
|
|
$this->assertSame('array<array-key, array<array-key, int>>', (string) Type::parseString('array<array<int>>'));
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testArrayWithUnion()
|
|
|
|
{
|
2017-05-27 02:05:57 +02:00
|
|
|
$this->assertSame('array<int|string, string>', (string) Type::parseString('array<int|string, string>'));
|
2016-09-22 06:31:07 +02:00
|
|
|
}
|
2016-09-09 22:38:32 +02:00
|
|
|
|
2017-01-13 20:07:23 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-09-22 06:31:07 +02:00
|
|
|
public function testGeneric()
|
|
|
|
{
|
2017-05-27 02:05:57 +02:00
|
|
|
$this->assertSame('B<int>', (string) Type::parseString('B<int>'));
|
2016-09-09 22:38:32 +02:00
|
|
|
}
|
|
|
|
|
2018-03-18 18:57:04 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testIntersection()
|
|
|
|
{
|
2018-09-07 17:09:07 +02:00
|
|
|
$this->assertSame('I1&I2&I3', (string) Type::parseString('I1&I2&I3'));
|
2018-03-18 18:57:04 +01:00
|
|
|
}
|
|
|
|
|
2018-03-22 22:55:36 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testIntersectionOrNull()
|
|
|
|
{
|
2019-01-05 06:15:53 +01:00
|
|
|
$this->assertSame('null|I1&I2', (string) Type::parseString('I1&I2|null'));
|
2018-03-22 22:55:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testNullOrIntersection()
|
|
|
|
{
|
|
|
|
$this->assertSame('null|I1&I2', (string) Type::parseString('null|I1&I2'));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testInteratorAndTraversable()
|
|
|
|
{
|
|
|
|
$this->assertSame('Iterator<int>&Traversable', (string) Type::parseString('Iterator<int>&Traversable'));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testTraversableAndIteratorOrNull()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
2019-01-05 06:15:53 +01:00
|
|
|
'null|Traversable&Iterator<int>',
|
2018-03-22 22:55:36 +01:00
|
|
|
(string) Type::parseString('Traversable&Iterator<int>|null')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-09-07 17:09:07 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testIntersectionAfterGeneric()
|
|
|
|
{
|
2018-12-05 16:36:42 +01:00
|
|
|
$this->assertSame('Countable&iterable<mixed, int>&I', (string) Type::parseString('Countable&iterable<int>&I'));
|
2018-09-07 17:09:07 +02:00
|
|
|
}
|
|
|
|
|
2017-01-13 20:07:23 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2018-03-21 01:19:26 +01:00
|
|
|
public function testPhpDocSimpleArray()
|
2016-10-30 02:57:03 +02:00
|
|
|
{
|
2019-01-05 06:15:53 +01:00
|
|
|
$this->assertSame('array<array-key, A>', (string) Type::parseString('A[]'));
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testPhpDocUnionArray()
|
|
|
|
{
|
2019-01-05 06:15:53 +01:00
|
|
|
$this->assertSame('array<array-key, A|B>', (string) Type::parseString('(A|B)[]'));
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testPhpDocMultiDimensionalArray()
|
|
|
|
{
|
2019-01-05 06:15:53 +01:00
|
|
|
$this->assertSame('array<array-key, array<array-key, A>>', (string) Type::parseString('A[][]'));
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testPhpDocMultidimensionalUnionArray()
|
|
|
|
{
|
2019-01-05 06:15:53 +01:00
|
|
|
$this->assertSame('array<array-key, array<array-key, A|B>>', (string) Type::parseString('(A|B)[][]'));
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
|
|
|
|
2018-03-23 02:21:04 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testPhpDocObjectLikeArray()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
2019-01-05 06:15:53 +01:00
|
|
|
'array<array-key, array{b:bool, d:string}>',
|
2018-03-23 02:21:04 +01:00
|
|
|
(string) Type::parseString('array{b:bool,d:string}[]')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testPhpDocUnionOfArrays()
|
|
|
|
{
|
2019-01-05 06:15:53 +01:00
|
|
|
$this->assertSame('array<array-key, A|B>', (string) Type::parseString('A[]|B[]'));
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testPhpDocUnionOfArraysOrObject()
|
|
|
|
{
|
2019-01-05 06:15:53 +01:00
|
|
|
$this->assertSame('array<array-key, A|B>|C', (string) Type::parseString('A[]|B[]|C'));
|
2016-10-30 02:57:03 +02:00
|
|
|
}
|
|
|
|
|
2018-03-05 22:06:06 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testPsalmOnlyAtomic()
|
|
|
|
{
|
|
|
|
$this->assertSame('class-string', (string) Type::parseString('class-string'));
|
|
|
|
}
|
|
|
|
|
2019-01-02 15:00:45 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testParamaterizedClassString()
|
|
|
|
{
|
|
|
|
$this->assertSame('class-string<A>', (string) Type::parseString('class-string<A>'));
|
|
|
|
}
|
|
|
|
|
2017-10-12 20:02:06 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testInvalidType()
|
|
|
|
{
|
|
|
|
Type::parseString('array(A)');
|
|
|
|
}
|
|
|
|
|
2018-03-23 03:28:06 +01:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBracketedUnionAndIntersection()
|
|
|
|
{
|
|
|
|
Type::parseString('(A|B)&C');
|
|
|
|
}
|
|
|
|
|
2018-06-30 20:25:32 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2018-06-30 19:09:05 +02:00
|
|
|
public function testBracketInUnion()
|
|
|
|
{
|
|
|
|
Type::parseString('null|(scalar|array|object)');
|
|
|
|
}
|
|
|
|
|
2017-01-13 20:07:23 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2018-03-21 01:19:26 +01:00
|
|
|
public function testObjectLikeWithSimpleArgs()
|
2016-09-22 06:31:07 +02:00
|
|
|
{
|
2017-05-27 02:05:57 +02:00
|
|
|
$this->assertSame('array{a:int, b:string}', (string) Type::parseString('array{a:int, b:string}'));
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
|
|
|
|
2019-01-18 06:56:24 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testObjectWithSimpleArgs()
|
|
|
|
{
|
|
|
|
$this->assertSame('object{a:int, b:string}', (string) Type::parseString('object{a:int, b:string}'));
|
|
|
|
}
|
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testObjectLikeWithUnionArgs()
|
|
|
|
{
|
2017-05-27 02:05:57 +02:00
|
|
|
$this->assertSame(
|
2016-11-13 17:24:25 +01:00
|
|
|
'array{a:int|string, b:string}',
|
2016-11-02 07:29:00 +01:00
|
|
|
(string) Type::parseString('array{a:int|string, b:string}')
|
|
|
|
);
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testObjectLikeWithGenericArgs()
|
|
|
|
{
|
2017-05-27 02:05:57 +02:00
|
|
|
$this->assertSame(
|
2016-11-13 17:24:25 +01:00
|
|
|
'array{a:array<int, string|int>, b:string}',
|
2016-11-02 07:29:00 +01:00
|
|
|
(string) Type::parseString('array{a:array<int, string|int>, b:string}')
|
|
|
|
);
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
2017-12-19 04:07:52 +01:00
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testObjectLikeWithIntKeysAndUnionArgs()
|
|
|
|
{
|
2017-12-19 04:07:52 +01:00
|
|
|
$this->assertSame(
|
2019-01-05 06:15:53 +01:00
|
|
|
'array{0:null|stdClass}',
|
2017-12-19 04:07:52 +01:00
|
|
|
(string)Type::parseString('array{stdClass|null}')
|
|
|
|
);
|
2018-03-21 01:19:26 +01:00
|
|
|
}
|
2017-12-19 04:07:52 +01:00
|
|
|
|
2018-03-21 01:19:26 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testObjectLikeWithIntKeysAndGenericArgs()
|
|
|
|
{
|
2017-12-19 04:07:52 +01:00
|
|
|
$this->assertSame(
|
2019-01-05 06:15:53 +01:00
|
|
|
'array{0:array<array-key, mixed>}',
|
2017-12-19 04:07:52 +01:00
|
|
|
(string)Type::parseString('array{array}')
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
'array{0:array<int, string>}',
|
|
|
|
(string)Type::parseString('array{array<int, string>}')
|
|
|
|
);
|
2018-03-17 04:37:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testObjectLikeOptional()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
|
|
|
'array{a:int, b?:int}',
|
|
|
|
(string)Type::parseString('array{a:int, b?:int}')
|
|
|
|
);
|
2016-09-22 06:31:07 +02:00
|
|
|
}
|
2018-03-27 04:13:10 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCallable()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
2018-04-08 18:57:56 +02:00
|
|
|
'callable(int, string):void',
|
2018-03-27 04:13:10 +02:00
|
|
|
(string)Type::parseString('callable(int, string) : void')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-12-08 20:10:06 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCallableReturningIntersection()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
|
|
|
'callable(int, string):I1&I2',
|
|
|
|
(string)Type::parseString('callable(int, string) : (I1&I2)')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-03-28 16:53:19 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testEmptyCallable()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
2018-04-08 18:57:56 +02:00
|
|
|
'callable():void',
|
2018-03-28 16:53:19 +02:00
|
|
|
(string)Type::parseString('callable() : void')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-03-27 04:13:10 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCallableWithUnionLastType()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
2018-04-08 18:57:56 +02:00
|
|
|
'callable(int, int|string):void',
|
2018-03-27 04:13:10 +02:00
|
|
|
(string)Type::parseString('callable(int, int|string) : void')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCallableWithVariadic()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
2018-04-08 18:57:56 +02:00
|
|
|
'callable(int, string...):void',
|
2018-03-27 06:12:41 +02:00
|
|
|
(string)Type::parseString('callable(int, string...) : void')
|
2018-03-27 04:13:10 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-04-18 19:23:48 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCallableThatReturnsACallable()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
|
|
|
'callable():callable():string',
|
|
|
|
(string)Type::parseString('callable() : callable() : string')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-11-06 14:53:14 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCallableThatReturnsACallableThatReturnsACallable()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
|
|
|
'callable():callable():callable():string',
|
|
|
|
(string)Type::parseString('callable() : callable() : callable() : string')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-04-19 01:04:06 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2018-04-19 01:00:08 +02:00
|
|
|
public function testCallableOrInt()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
|
|
|
'callable(string):void|int',
|
|
|
|
(string)Type::parseString('callable(string):void|int')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-03-27 04:13:10 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCallableWithBadVariadic()
|
|
|
|
{
|
2018-03-27 06:12:41 +02:00
|
|
|
Type::parseString('callable(int, ...string) : void');
|
|
|
|
}
|
|
|
|
|
2018-04-16 00:16:31 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCallableWithTrailingColon()
|
|
|
|
{
|
|
|
|
Type::parseString('callable(int):');
|
|
|
|
}
|
|
|
|
|
2018-03-27 06:12:41 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCallableWithAnotherBadVariadic()
|
|
|
|
{
|
|
|
|
Type::parseString('callable(int, string..) : void');
|
2018-03-27 04:13:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCallableWithVariadicAndDefault()
|
|
|
|
{
|
2018-03-27 06:12:41 +02:00
|
|
|
Type::parseString('callable(int, string...=) : void');
|
2018-03-27 04:13:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBadVariadic()
|
|
|
|
{
|
2018-03-27 06:12:41 +02:00
|
|
|
Type::parseString('string...');
|
2018-03-27 04:13:10 +02:00
|
|
|
}
|
|
|
|
|
2018-03-27 20:43:39 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBadFullStop()
|
|
|
|
{
|
|
|
|
Type::parseString('string.');
|
|
|
|
}
|
|
|
|
|
2018-04-05 20:11:57 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBadSemicolon()
|
|
|
|
{
|
|
|
|
Type::parseString('string;');
|
|
|
|
}
|
|
|
|
|
2019-01-29 16:34:31 +01:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBadGenericString()
|
|
|
|
{
|
|
|
|
Type::parseString('string<T>');
|
|
|
|
}
|
|
|
|
|
2018-06-11 23:23:28 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBadAmpersand()
|
|
|
|
{
|
|
|
|
Type::parseString('&array');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBadColon()
|
|
|
|
{
|
|
|
|
Type::parseString(':array');
|
|
|
|
}
|
|
|
|
|
2018-09-06 04:36:32 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBadBrackets()
|
|
|
|
{
|
|
|
|
Type::parseString('max(a)');
|
|
|
|
}
|
|
|
|
|
2018-09-06 04:40:52 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testMoreBadBrackets()
|
|
|
|
{
|
|
|
|
Type::parseString('max(a):void');
|
|
|
|
}
|
|
|
|
|
2018-09-06 06:41:07 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testGeneratorWithWBadBrackets()
|
|
|
|
{
|
|
|
|
Type::parseString('Generator{string, A}');
|
|
|
|
}
|
|
|
|
|
2018-06-11 23:23:28 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBadEquals()
|
|
|
|
{
|
|
|
|
Type::parseString('=array');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBadBar()
|
|
|
|
{
|
|
|
|
Type::parseString('|array');
|
|
|
|
}
|
|
|
|
|
2018-06-12 15:11:13 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testBadColonDash()
|
|
|
|
{
|
|
|
|
Type::parseString('array|string:-');
|
|
|
|
}
|
|
|
|
|
2018-03-29 08:20:19 +02:00
|
|
|
/**
|
|
|
|
* @expectedException \Psalm\Exception\TypeParseTreeException
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testDoubleBar()
|
|
|
|
{
|
|
|
|
Type::parseString('PDO||Closure|numeric');
|
|
|
|
}
|
|
|
|
|
2018-03-27 04:13:10 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCallableWithDefault()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
2018-04-08 18:57:56 +02:00
|
|
|
'callable(int, string=):void',
|
2018-03-27 04:13:10 +02:00
|
|
|
(string)Type::parseString('callable(int, string=) : void')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-11-06 14:53:14 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testNestedCallable()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
|
|
|
'callable(callable(A):B):C',
|
|
|
|
(string)Type::parseString('callable(callable(A):B):C')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-03-27 04:13:10 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCallableWithoutReturn()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
|
|
|
'callable(int, string)',
|
|
|
|
(string)Type::parseString('callable(int, string)')
|
|
|
|
);
|
|
|
|
}
|
2018-04-19 01:00:08 +02:00
|
|
|
|
2019-01-29 16:34:31 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCombineLiteralStringWithClassString()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
|
|
|
'string',
|
|
|
|
(string)Type::parseString('"array"|class-string')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testCombineLiteralClassStringWithClassString()
|
|
|
|
{
|
|
|
|
$this->assertSame(
|
|
|
|
'class-string',
|
|
|
|
(string)Type::parseString('A::class|class-string')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-04-20 21:22:48 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testVeryLargeType()
|
|
|
|
{
|
|
|
|
$very_large_type = 'array{a:Closure():(array<mixed, mixed>|null), b?:Closure():array<mixed, mixed>, c?:Closure():array<mixed, mixed>, d?:Closure():array<mixed, mixed>, e?:Closure():(array{f:null|string, g:null|string, h:null|string, i:string, j:mixed, k:mixed, l:mixed, m:mixed, n:bool, o?:array{0:string}}|null), p?:Closure():(array{f:null|string, g:null|string, h:null|string, q:string, i:string, j:mixed, k:mixed, l:mixed, m:mixed, n:bool, o?:array{0:string}}|null), r?:Closure():(array<mixed, mixed>|null), s:array<mixed, mixed>}|null';
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
$very_large_type,
|
|
|
|
(string) Type::parseString($very_large_type)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-05-20 23:19:53 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testEnum()
|
|
|
|
{
|
2019-01-28 00:34:13 +01:00
|
|
|
$docblock_type = Type::parseString('( \'foo\\\'with\' | "bar\"bar" | "baz" | "bat\\\\" | \'bang bang\' | 1 | 2 | 3 | 4.5)');
|
2018-05-20 23:19:53 +02:00
|
|
|
|
|
|
|
$resolved_type = new Type\Union([
|
|
|
|
new Type\Atomic\TLiteralString('foo\'with'),
|
|
|
|
new Type\Atomic\TLiteralString('bar"bar'),
|
|
|
|
new Type\Atomic\TLiteralString('baz'),
|
|
|
|
new Type\Atomic\TLiteralString('bat\\'),
|
|
|
|
new Type\Atomic\TLiteralString('bang bang'),
|
|
|
|
new Type\Atomic\TLiteralInt(1),
|
|
|
|
new Type\Atomic\TLiteralInt(2),
|
2019-01-28 00:34:13 +01:00
|
|
|
new Type\Atomic\TLiteralInt(3),
|
|
|
|
new Type\Atomic\TLiteralFloat(4.5)
|
2018-05-20 23:19:53 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
$this->assertSame($resolved_type->getId(), $docblock_type->getId());
|
|
|
|
}
|
|
|
|
|
2018-05-21 06:46:56 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testEnumWithoutSpaces()
|
|
|
|
{
|
2019-01-28 00:34:13 +01:00
|
|
|
$docblock_type = Type::parseString('\'foo\\\'with\'|"bar\"bar"|"baz"|"bat\\\\"|\'bang bang\'|1|2|3|4.5');
|
2018-05-21 06:46:56 +02:00
|
|
|
|
|
|
|
$resolved_type = new Type\Union([
|
|
|
|
new Type\Atomic\TLiteralString('foo\'with'),
|
|
|
|
new Type\Atomic\TLiteralString('bar"bar'),
|
|
|
|
new Type\Atomic\TLiteralString('baz'),
|
|
|
|
new Type\Atomic\TLiteralString('bat\\'),
|
|
|
|
new Type\Atomic\TLiteralString('bang bang'),
|
|
|
|
new Type\Atomic\TLiteralInt(1),
|
|
|
|
new Type\Atomic\TLiteralInt(2),
|
2019-01-28 00:34:13 +01:00
|
|
|
new Type\Atomic\TLiteralInt(3),
|
|
|
|
new Type\Atomic\TLiteralFloat(4.5)
|
2018-05-21 06:46:56 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
$this->assertSame($resolved_type->getId(), $docblock_type->getId());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testEnumWithClassConstants()
|
|
|
|
{
|
|
|
|
$docblock_type = Type::parseString('("baz" | One2::TWO_THREE | Foo::BAR_BAR | Bat\Bar::BAZ_BAM)');
|
|
|
|
|
|
|
|
$resolved_type = new Type\Union([
|
|
|
|
new Type\Atomic\TLiteralString('baz'),
|
|
|
|
new Type\Atomic\TScalarClassConstant('One2', 'TWO_THREE'),
|
|
|
|
new Type\Atomic\TScalarClassConstant('Foo', 'BAR_BAR'),
|
|
|
|
new Type\Atomic\TScalarClassConstant('Bat\\Bar', 'BAZ_BAM'),
|
|
|
|
]);
|
|
|
|
|
|
|
|
$this->assertSame($resolved_type->getId(), $docblock_type->getId());
|
|
|
|
}
|
|
|
|
|
2019-01-27 23:16:22 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testReflectionTypeParse()
|
|
|
|
{
|
|
|
|
/** @psalm-suppress UnusedParam */
|
2019-01-27 23:26:32 +01:00
|
|
|
function someFunction(string $param, array $param2, int $param3 = null) : string
|
2019-01-27 23:16:22 +01:00
|
|
|
{
|
|
|
|
return "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
$reflectionFunc = new \ReflectionFunction('Psalm\Tests\someFunction');
|
|
|
|
$reflectionParams = $reflectionFunc->getParameters();
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
'string',
|
2019-01-27 23:26:32 +01:00
|
|
|
(string) \Psalm\Codebase::getPsalmTypeFromReflection($reflectionParams[0]->getType())
|
2019-01-27 23:16:22 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
'array<array-key, mixed>',
|
2019-01-27 23:26:32 +01:00
|
|
|
(string) \Psalm\Codebase::getPsalmTypeFromReflection($reflectionParams[1]->getType())
|
2019-01-27 23:16:22 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
$this->assertSame(
|
2019-01-27 23:27:12 +01:00
|
|
|
'int|null',
|
2019-01-27 23:26:32 +01:00
|
|
|
(string) \Psalm\Codebase::getPsalmTypeFromReflection($reflectionParams[2]->getType())
|
2019-01-27 23:16:22 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
'string',
|
2019-01-27 23:26:32 +01:00
|
|
|
(string) \Psalm\Codebase::getPsalmTypeFromReflection($reflectionFunc->getReturnType())
|
2019-01-27 23:16:22 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-04-19 01:00:08 +02:00
|
|
|
/**
|
|
|
|
* @dataProvider providerTestValidCallMapType
|
|
|
|
*
|
2018-04-19 01:04:06 +02:00
|
|
|
* @param string $return_type
|
|
|
|
* @param string $param_type_1
|
|
|
|
* @param string $param_type_2
|
|
|
|
* @param string $param_type_3
|
|
|
|
* @param string $param_type_4
|
2018-04-19 01:00:08 +02:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function testValidCallMapType(
|
|
|
|
$return_type,
|
|
|
|
$param_type_1 = '',
|
|
|
|
$param_type_2 = '',
|
|
|
|
$param_type_3 = '',
|
|
|
|
$param_type_4 = ''
|
|
|
|
) {
|
|
|
|
if ($return_type && $return_type !== 'void') {
|
2019-01-09 03:44:50 +01:00
|
|
|
if (stripos($return_type, 'oci-') !== false) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-19 01:00:08 +02:00
|
|
|
\Psalm\Type::parseString($return_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($param_type_1 && $param_type_1 !== 'mixed') {
|
2019-01-09 03:44:50 +01:00
|
|
|
if (stripos($param_type_1, 'oci-') !== false) {
|
2018-04-19 01:00:08 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
\Psalm\Type::parseString($param_type_1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($param_type_2 && $param_type_2 !== 'mixed') {
|
|
|
|
\Psalm\Type::parseString($param_type_2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($param_type_3 && $param_type_3 !== 'mixed') {
|
|
|
|
\Psalm\Type::parseString($param_type_3);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($param_type_4 && $param_type_4 !== 'mixed') {
|
|
|
|
\Psalm\Type::parseString($param_type_4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-04-19 01:04:06 +02:00
|
|
|
* @return array<string, array<int|string, string>>
|
2018-04-19 01:00:08 +02:00
|
|
|
*/
|
|
|
|
public function providerTestValidCallMapType()
|
|
|
|
{
|
2018-11-12 16:46:55 +01:00
|
|
|
return \Psalm\Internal\Codebase\CallMap::getCallMap();
|
2018-04-19 01:00:08 +02:00
|
|
|
}
|
2016-09-09 22:38:32 +02:00
|
|
|
}
|