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