1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00
psalm/tests/TypeReconciliationTest.php

372 lines
10 KiB
PHP
Raw Normal View History

2016-06-17 22:05:28 +02:00
<?php
2016-07-26 00:37:44 +02:00
namespace Psalm\Tests;
2016-06-17 22:05:28 +02:00
2016-10-11 04:49:43 +02:00
use PhpParser\ParserFactory;
2016-06-17 22:05:28 +02:00
use PHPUnit_Framework_TestCase;
2016-11-02 07:29:00 +01:00
use Psalm\Checker\FileChecker;
2016-10-11 04:49:43 +02:00
use Psalm\Checker\TypeChecker;
2016-11-02 07:29:00 +01:00
use Psalm\Config;
use Psalm\Context;
2016-10-11 04:49:43 +02:00
use Psalm\Type;
2016-06-17 22:05:28 +02:00
class TypeReconciliationTest extends PHPUnit_Framework_TestCase
{
/** @var \PhpParser\Parser */
2016-11-02 07:29:00 +01:00
protected static $parser;
2016-10-11 04:49:43 +02:00
public static function setUpBeforeClass()
{
2016-11-02 07:29:00 +01:00
self::$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
2016-10-11 04:49:43 +02:00
2016-12-14 18:28:38 +01:00
$config = new TestConfig();
2016-10-11 04:49:43 +02:00
}
public function setUp()
{
2016-11-02 07:29:00 +01:00
FileChecker::clearCache();
2016-10-11 04:49:43 +02:00
}
2016-06-17 22:05:28 +02:00
public function testNotNull()
{
$this->assertEquals(
2016-07-12 06:53:36 +02:00
'MyObject',
(string) TypeChecker::reconcileTypes('!null', Type::parseString('MyObject'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
2016-07-12 06:53:36 +02:00
'MyObject',
(string) TypeChecker::reconcileTypes('!null', Type::parseString('MyObject|null'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
2016-07-12 06:53:36 +02:00
'MyObject|false',
(string) TypeChecker::reconcileTypes('!null', Type::parseString('MyObject|false'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
'mixed',
(string) TypeChecker::reconcileTypes('!null', Type::parseString('mixed'))
);
}
public function testNotEmpty()
{
$this->assertEquals(
2016-07-12 06:53:36 +02:00
'MyObject',
(string) TypeChecker::reconcileTypes('!empty', Type::parseString('MyObject'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
2016-07-12 06:53:36 +02:00
'MyObject',
(string) TypeChecker::reconcileTypes('!empty', Type::parseString('MyObject|null'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
2016-07-12 06:53:36 +02:00
'MyObject',
(string) TypeChecker::reconcileTypes('!empty', Type::parseString('MyObject|false'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
'mixed',
(string) TypeChecker::reconcileTypes('!empty', Type::parseString('mixed'))
);
2016-07-26 00:31:03 +02:00
// @todo in the future this should also work
/*
2016-06-17 22:05:28 +02:00
$this->assertEquals(
2016-07-12 06:53:36 +02:00
'MyObject|true',
(string) TypeChecker::reconcileTypes('!empty', Type::parseString('MyObject|bool'))
2016-06-17 22:05:28 +02:00
);
2016-07-26 00:31:03 +02:00
*/
2016-06-17 22:05:28 +02:00
}
public function testNull()
{
$this->assertEquals(
'null',
2016-07-12 06:53:36 +02:00
(string) TypeChecker::reconcileTypes('null', Type::parseString('MyObject|null'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
'null',
(string) TypeChecker::reconcileTypes('null', Type::parseString('mixed'))
);
}
public function testEmpty()
{
$this->assertEquals(
'null',
2016-07-12 06:53:36 +02:00
(string) TypeChecker::reconcileTypes('empty', Type::parseString('MyObject'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
'false',
2016-07-12 06:53:36 +02:00
(string) TypeChecker::reconcileTypes('empty', Type::parseString('MyObject|false'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
'false',
2016-07-12 06:53:36 +02:00
(string) TypeChecker::reconcileTypes('empty', Type::parseString('MyObject|bool'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
'mixed',
(string) TypeChecker::reconcileTypes('empty', Type::parseString('mixed'))
);
/** @var Type\Union */
2016-06-17 22:05:28 +02:00
$reconciled = TypeChecker::reconcileTypes('empty', Type::parseString('bool'));
$this->assertEquals('false', (string) $reconciled);
2016-07-26 00:37:44 +02:00
$this->assertInstanceOf('Psalm\Type\Atomic', $reconciled->types['false']);
2016-06-17 22:05:28 +02:00
}
2016-07-12 06:53:36 +02:00
public function testNotMyObject()
2016-06-17 22:05:28 +02:00
{
$this->assertEquals(
'bool',
2016-07-12 06:53:36 +02:00
(string) TypeChecker::reconcileTypes('!MyObject', Type::parseString('MyObject|bool'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
'null',
2016-07-12 06:53:36 +02:00
(string) TypeChecker::reconcileTypes('!MyObject', Type::parseString('MyObject|null'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
2016-07-12 06:53:36 +02:00
'MyObjectB',
(string) TypeChecker::reconcileTypes('!MyObjectA', Type::parseString('MyObjectA|MyObjectB'))
2016-06-17 22:05:28 +02:00
);
}
2016-07-12 06:53:36 +02:00
public function testMyObject()
{
2016-06-17 22:05:28 +02:00
$this->assertEquals(
2016-07-12 06:53:36 +02:00
'MyObject',
(string) TypeChecker::reconcileTypes('MyObject', Type::parseString('MyObject|bool'))
2016-06-17 22:05:28 +02:00
);
$this->assertEquals(
2016-07-12 06:53:36 +02:00
'MyObjectA',
(string) TypeChecker::reconcileTypes('MyObjectA', Type::parseString('MyObjectA|MyObjectB'))
2016-06-17 22:05:28 +02:00
);
}
public function testArrayContains()
{
$this->assertTrue(
TypeChecker::isContainedBy(
Type::parseString('array<string>'),
Type::parseString('array')
)
);
$this->assertTrue(
TypeChecker::isContainedBy(
Type::parseString('array<Exception>'),
Type::parseString('array')
)
);
$this->assertTrue(
TypeChecker::isContainedBy(
Type::parseString('array<UnexpectedValueException>'),
Type::parseString('array<Exception>')
)
);
$this->assertFalse(
TypeChecker::isContainedBy(
Type::parseString('array<ValueException>'),
Type::parseString('array<UnexpectedException>')
)
);
}
2016-12-24 00:52:34 +01:00
public function testNumeric()
{
$this->assertEquals(
'string',
(string) TypeChecker::reconcileTypes('numeric', Type::parseString('string'))
);
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage TypeDoesNotContainType
*/
public function testMakeNonNullableNull()
{
$stmts = self::$parser->parse('<?php
class A { }
$a = new A();
if ($a === null) {
}
');
$file_checker = new FileChecker('somefile.php', $stmts);
$context = new Context('somefile.php');
$file_checker->check(true, true, $context);
2016-06-17 22:05:28 +02:00
}
2016-10-11 04:49:43 +02:00
2016-12-12 05:41:11 +01:00
/**
* @expectedException \Psalm\Exception\CodeException
2016-12-12 19:50:46 +01:00
* @expectedExceptionMessage TypeDoesNotContainType
*/
public function testMakeInstanceOfThingInElseif()
{
$stmts = self::$parser->parse('<?php
class A { }
class B { }
class C { }
$a = rand(0, 10) > 5 ? new A() : new B();
if ($a instanceof A) {
} elseif ($a instanceof C) {
}
');
$file_checker = new FileChecker('somefile.php', $stmts);
$context = new Context('somefile.php');
$file_checker->check(true, true, $context);
}
/**
* @expectedException \Psalm\Exception\CodeException
2016-12-12 05:41:11 +01:00
* @expectedExceptionMessage FailedTypeResolution
*/
public function testFailedTypeResolution()
{
$stmts = self::$parser->parse('<?php
class A { }
$a = new A();
if ($a instanceof A) {
}
');
$file_checker = new FileChecker('somefile.php', $stmts);
$context = new Context('somefile.php');
$file_checker->check(true, true, $context);
}
2016-10-11 04:49:43 +02:00
public function testNotInstanceOf()
{
2016-11-02 07:29:00 +01:00
$stmts = self::$parser->parse('<?php
2016-10-11 04:49:43 +02:00
class A { }
class B extends A { }
$out = null;
if ($a instanceof B) {
// do something
}
else {
$out = $a;
}
');
2016-11-02 07:29:00 +01:00
$file_checker = new FileChecker('somefile.php', $stmts);
2016-10-11 04:49:43 +02:00
$context = new Context('somefile.php');
2016-10-15 19:10:05 +02:00
$context->vars_in_scope['$a'] = Type::parseString('A');
2016-10-11 04:49:43 +02:00
$file_checker->check(true, true, $context);
2016-10-15 19:10:05 +02:00
$this->assertEquals('null|A', (string) $context->vars_in_scope['$out']);
2016-10-11 04:49:43 +02:00
}
public function testNotInstanceOfProperty()
{
2016-11-02 07:29:00 +01:00
$stmts = self::$parser->parse('<?php
2016-10-11 04:49:43 +02:00
class B { }
class C extends B { }
class A {
/** @var B */
public $foo;
}
$out = null;
if ($a->foo instanceof C) {
// do something
}
else {
$out = $a->foo;
}
');
2016-11-02 07:29:00 +01:00
$file_checker = new FileChecker('somefile.php', $stmts);
2016-10-11 04:49:43 +02:00
$context = new Context('somefile.php');
2016-10-15 19:10:05 +02:00
$context->vars_in_scope['$a'] = Type::parseString('A');
2016-10-11 04:49:43 +02:00
$file_checker->check(true, true, $context);
2016-10-15 19:10:05 +02:00
$this->assertEquals('null|B', (string) $context->vars_in_scope['$out']);
}
public function testNotInstanceOfPropertyElseif()
{
2016-11-02 07:29:00 +01:00
$stmts = self::$parser->parse('<?php
2016-10-15 19:10:05 +02:00
class B { }
class C extends B { }
class A {
/** @var string|B */
public $foo;
}
$out = null;
if (is_string($a->foo)) {
}
elseif ($a->foo instanceof C) {
// do something
}
else {
$out = $a->foo;
}
');
2016-11-02 07:29:00 +01:00
$file_checker = new FileChecker('somefile.php', $stmts);
2016-10-15 19:10:05 +02:00
$context = new Context('somefile.php');
$context->vars_in_scope['$a'] = Type::parseString('A');
$file_checker->check(true, true, $context);
$this->assertEquals('null|B', (string) $context->vars_in_scope['$out']);
2016-10-11 04:49:43 +02:00
}
public function testTypeArguments()
{
2016-11-02 07:29:00 +01:00
$stmts = self::$parser->parse('<?php
2016-10-11 04:49:43 +02:00
$a = min(0, 1);
$b = min([0, 1]);
$c = min("a", "b");
');
2016-11-02 07:29:00 +01:00
$file_checker = new FileChecker('somefile.php', $stmts);
2016-10-11 04:49:43 +02:00
$context = new Context('somefile.php');
$file_checker->check(true, true, $context);
$this->assertEquals('int', (string) $context->vars_in_scope['$a']);
$this->assertEquals('int', (string) $context->vars_in_scope['$b']);
$this->assertEquals('string', (string) $context->vars_in_scope['$c']);
2016-10-11 04:49:43 +02:00
}
2016-12-24 00:52:34 +01:00
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage TypeDoesNotContainType
*/
public function testTypeTransformation()
{
$stmts = self::$parser->parse('<?php
$a = "5";
if (is_numeric($a)) {
if (is_int($a)) {
echo $a;
}
}
');
$file_checker = new FileChecker('somefile.php', $stmts);
$context = new Context('somefile.php');
$file_checker->check(true, true, $context);
}
2016-06-17 22:05:28 +02:00
}