1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +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], '::') && !strpos($line_parts[0], '::')
&& $line_parts[0][0] !== '{' && $line_parts[0][0] !== '{'
) { ) {
if ($line_parts[0][0] === '$' && $line_parts[0] !== '$this') { if ($line_parts[0][0] === '$' && !preg_match('/^\$this(\||$)/', $line_parts[0])) {
if ($line_parts[0][0] === '$' && $line_parts[0] !== '$this') { throw new IncorrectDocblockException('Misplaced variable');
throw new IncorrectDocblockException('Misplaced variable');
}
} }
$info->return_type = $line_parts[0]; $info->return_type = $line_parts[0];
@ -202,7 +200,7 @@ class CommentChecker
$line_parts[1] = substr($line_parts[1], 1); $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'); throw new IncorrectDocblockException('Misplaced variable');
} }
@ -351,7 +349,7 @@ class CommentChecker
$line_parts[1] = substr($line_parts[1], 1); $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'); throw new IncorrectDocblockException('Misplaced variable');
} }

View File

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

View File

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

View File

@ -18,7 +18,13 @@ trait GenericTrait
$s .= $type_param . ', '; $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 . '[]'; 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 . return $base_value .
'<' . '<' .
implode( implode(
@ -69,7 +92,7 @@ trait GenericTrait
$this->type_params $this->type_params
) )
) . ) .
'>'; '>' . $extra_types;
} }
public function __clone() public function __clone()

View File

@ -141,6 +141,11 @@ class ParseTree
continue; 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 = new ParseTree\UnionTree($current_parent);
$new_parent_leaf->children = [$current_leaf]; $new_parent_leaf->children = [$current_leaf];
$current_leaf->parent = $new_parent_leaf; $current_leaf->parent = $new_parent_leaf;
@ -202,6 +207,10 @@ class ParseTree
throw new TypeParseTreeException('Cannot process bracket yet'); throw new TypeParseTreeException('Cannot process bracket yet');
default: default:
if ($type_token === '$this') {
$type_token = 'static';
}
$new_leaf = new ParseTree\Value( $new_leaf = new ParseTree\Value(
$type_token, $type_token,
$new_parent $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' => [ 'inheritMultipleInterfacesWithDocblocks' => [
'<?php '<?php
interface I1 { interface I1 {

View File

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

View File

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