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:
parent
ccbe9980f5
commit
cc3aafe4c4
@ -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');
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user