1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Improve intersection type parsing

This commit is contained in:
Matt Brown 2018-03-22 17:55:36 -04:00
parent ccbe9980f5
commit cc3aafe4c4
8 changed files with 112 additions and 14 deletions

View File

@ -157,10 +157,8 @@ class CommentChecker
&& !strpos($line_parts[0], '::')
&& $line_parts[0][0] !== '{'
) {
if ($line_parts[0][0] === '$' && $line_parts[0] !== '$this') {
if ($line_parts[0][0] === '$' && $line_parts[0] !== '$this') {
throw new IncorrectDocblockException('Misplaced variable');
}
if ($line_parts[0][0] === '$' && !preg_match('/^\$this(\||$)/', $line_parts[0])) {
throw new IncorrectDocblockException('Misplaced variable');
}
$info->return_type = $line_parts[0];
@ -202,7 +200,7 @@ class CommentChecker
$line_parts[1] = substr($line_parts[1], 1);
}
if ($line_parts[0][0] === '$' && $line_parts[0] !== '$this') {
if ($line_parts[0][0] === '$' && !preg_match('/^\$this(\||$)/', $line_parts[0])) {
throw new IncorrectDocblockException('Misplaced variable');
}
@ -351,7 +349,7 @@ class CommentChecker
$line_parts[1] = substr($line_parts[1], 1);
}
if ($line_parts[0][0] === '$' && $line_parts[0] !== '$this') {
if ($line_parts[0][0] === '$' && !preg_match('/^\$this(\||$)/', $line_parts[0])) {
throw new IncorrectDocblockException('Misplaced variable');
}

View File

@ -211,7 +211,7 @@ abstract class Type
$first_type->extra_types = $intersection_types;
return new Type\Union([$first_type]);
return $first_type;
}
if ($parse_tree instanceof ParseTree\ObjectLikeTree) {
@ -358,10 +358,6 @@ abstract class Type
!isset($template_types[$return_type_token])
) {
if ($return_type_token[0] === '$') {
if ($return_type === '$this') {
$return_type_token = 'static';
}
continue;
}

View File

@ -108,6 +108,9 @@ abstract class Atomic
case 'class-string':
return new TClassString();
case '$this':
return new TNamedObject('static');
default:
if (strpos($value, '-')) {
throw new \Psalm\Exception\TypeParseTreeException('no hyphens allowed');

View File

@ -18,7 +18,13 @@ trait GenericTrait
$s .= $type_param . ', ';
}
return $this->value . '<' . substr($s, 0, -2) . '>';
$extra_types = '';
if ($this instanceof TNamedObject && $this->extra_types) {
$extra_types = '&' . implode('&', $this->extra_types);
}
return $this->value . '<' . substr($s, 0, -2) . '>' . $extra_types;
}
/**
@ -55,6 +61,23 @@ trait GenericTrait
return $value_type_string . '[]';
}
$extra_types = '';
if ($this instanceof TNamedObject && $this->extra_types) {
$extra_types = '&' . implode(
'&',
array_map(
/**
* @return string
*/
function (Atomic $extra_type) use ($namespace, $aliased_classes, $this_class) {
return $extra_type->toNamespacedString($namespace, $aliased_classes, $this_class, false);
},
$this->extra_types
)
);
}
return $base_value .
'<' .
implode(
@ -69,7 +92,7 @@ trait GenericTrait
$this->type_params
)
) .
'>';
'>' . $extra_types;
}
public function __clone()

View File

@ -141,6 +141,11 @@ class ParseTree
continue;
}
if ($current_parent && $current_parent instanceof ParseTree\IntersectionTree) {
$current_leaf = $current_parent;
$current_parent = $current_leaf->parent;
}
$new_parent_leaf = new ParseTree\UnionTree($current_parent);
$new_parent_leaf->children = [$current_leaf];
$current_leaf->parent = $new_parent_leaf;
@ -202,6 +207,10 @@ class ParseTree
throw new TypeParseTreeException('Cannot process bracket yet');
default:
if ($type_token === '$this') {
$type_token = 'static';
}
$new_leaf = new ParseTree\Value(
$type_token,
$new_parent

View File

@ -338,6 +338,21 @@ class InterfaceTest extends TestCase
}
}',
],
'implementThisReturn' => [
'<?php
class A {}
interface I {
/** @return A */
public function foo();
}
class B extends A implements I {
/** @return $this */
public function foo() {
return $this;
}
}',
],
'inheritMultipleInterfacesWithDocblocks' => [
'<?php
interface I1 {

View File

@ -13,6 +13,22 @@ class TypeParseTest extends TestCase
//parent::setUp();
}
/**
* @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'));
}
/**
* @return void
*/
@ -87,6 +103,41 @@ class TypeParseTest extends TestCase
$this->assertSame('I1&I2', (string) Type::parseString('I1&I2'));
}
/**
* @return void
*/
public function testIntersectionOrNull()
{
$this->assertSame('I1&I2|null', (string) Type::parseString('I1&I2|null'));
}
/**
* @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(
'Traversable&Iterator<int>|null',
(string) Type::parseString('Traversable&Iterator<int>|null')
);
}
/**
* @return void
*/

View File

@ -570,7 +570,10 @@ class TypeReconciliationTest extends TestCase
function takesIandA($a): void {}
class A {
public function foo(): void {
/**
* @return A&I|null
*/
public function foo() {
if ($this instanceof I) {
$this->bar();
$this->bat();