1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +01:00

Fix #1813 - convert object&Foo into Foo after template resolution

This commit is contained in:
Brown 2019-06-19 12:00:07 -04:00
parent 7155ed4e06
commit 0246f600f4
10 changed files with 132 additions and 25 deletions

View File

@ -580,7 +580,9 @@ class ForeachAnalyzer
}
foreach ($iterator_atomic_types as $iterator_atomic_type) {
if ($iterator_atomic_type instanceof Type\Atomic\TTemplateParam) {
if ($iterator_atomic_type instanceof Type\Atomic\TTemplateParam
|| $iterator_atomic_type instanceof Type\Atomic\TObjectWithProperties
) {
throw new \UnexpectedValueException('Shouldnt get a generic param here');
}

View File

@ -2823,9 +2823,12 @@ class CallAnalyzer
if ($type_part->extra_types) {
foreach ($type_part->extra_types as $extra_type) {
if ($extra_type instanceof Type\Atomic\TTemplateParam) {
if ($extra_type instanceof Type\Atomic\TTemplateParam
|| $extra_type instanceof Type\Atomic\TObjectWithProperties
) {
throw new \UnexpectedValueException('Shouldnt get a generic param here');
}
$method_id .= '&' . $extra_type->value . '::' . $method_name_arg->value;
}
}

View File

@ -452,6 +452,8 @@ class TypeAnalyzer
foreach ($intersection_container_types as $intersection_container_type) {
if ($intersection_container_type instanceof TIterable) {
$intersection_container_type_lower = 'iterable';
} elseif ($intersection_container_type instanceof TObjectWithProperties) {
$intersection_container_type_lower = 'object';
} elseif ($intersection_container_type instanceof TTemplateParam) {
if ($intersection_container_type->as->isMixed()) {
continue;
@ -485,6 +487,8 @@ class TypeAnalyzer
foreach ($intersection_input_types as $intersection_input_type) {
if ($intersection_input_type instanceof TIterable) {
$intersection_input_type_lower = 'iterable';
} elseif ($intersection_input_type instanceof TObjectWithProperties) {
$intersection_input_type_lower = 'object';
} elseif ($intersection_input_type instanceof TTemplateParam) {
if ($intersection_input_type->as->isMixed()) {
continue;

View File

@ -99,7 +99,7 @@ class TypeCombination
private $floats = [];
/**
* @var array<string, TNamedObject|TTemplateParam|TIterable>|null
* @var array<string, TNamedObject|TTemplateParam|TIterable|TObject>|null
*/
private $extra_types;
@ -654,7 +654,11 @@ class TypeCombination
}
}
if ($type instanceof TNamedObject || $type instanceof TTemplateParam || $type instanceof TIterable) {
if ($type instanceof TNamedObject
|| $type instanceof TTemplateParam
|| $type instanceof TIterable
|| $type instanceof Type\Atomic\TObjectWithProperties
) {
if ($type->extra_types) {
$combination->extra_types = array_merge(
$combination->extra_types ?: [],

View File

@ -395,9 +395,10 @@ abstract class Type
$keyed_intersection_types = [];
foreach ($intersection_types as $intersection_type) {
if (!$intersection_type instanceof TNamedObject
if (!$intersection_type instanceof TIterable
&& !$intersection_type instanceof TNamedObject
&& !$intersection_type instanceof TTemplateParam
&& !$intersection_type instanceof TIterable
&& !$intersection_type instanceof TObjectWithProperties
) {
throw new TypeParseTreeException(
'Intersection types must all be objects, ' . get_class($intersection_type) . ' provided'
@ -1435,10 +1436,12 @@ abstract class Type
foreach ($type_2->getTypes() as $type_2_atomic) {
if (($type_1_atomic instanceof TIterable
|| $type_1_atomic instanceof TNamedObject
|| $type_1_atomic instanceof TTemplateParam)
|| $type_1_atomic instanceof TTemplateParam
|| $type_1_atomic instanceof TObjectWithProperties)
&& ($type_2_atomic instanceof TIterable
|| $type_2_atomic instanceof TNamedObject
|| $type_2_atomic instanceof TTemplateParam)
|| $type_2_atomic instanceof TTemplateParam
|| $type_2_atomic instanceof TObjectWithProperties)
) {
if (!$type_1_atomic->extra_types) {
$type_1_atomic->extra_types = [];

View File

@ -930,6 +930,7 @@ abstract class Atomic
if ($this instanceof TNamedObject
|| $this instanceof TTemplateParam
|| $this instanceof TIterable
|| $this instanceof Type\Atomic\TObjectWithProperties
) {
if ($this->extra_types) {
foreach ($this->extra_types as &$type) {

View File

@ -8,21 +8,19 @@ use Psalm\Codebase;
trait HasIntersectionTrait
{
/**
* @var array<string, TNamedObject|TTemplateParam|TIterable>|null
* @var array<string, TNamedObject|TTemplateParam|TIterable|TObjectWithProperties>|null
*/
public $extra_types;
/**
* @param array<string, string> $aliased_classes
*
* @return string
*/
private function getNamespacedIntersectionTypes(
?string $namespace,
array $aliased_classes,
?string $this_class,
bool $use_phpdoc_format
) {
) : string {
if (!$this->extra_types) {
return '';
}
@ -31,7 +29,7 @@ trait HasIntersectionTrait
'&',
array_map(
/**
* @param TNamedObject|TTemplateParam|TIterable $extra_type
* @param TNamedObject|TTemplateParam|TIterable|TObjectWithProperties $extra_type
* @return string
*/
function (Atomic $extra_type) use (
@ -53,29 +51,25 @@ trait HasIntersectionTrait
}
/**
* @param TNamedObject $type
*
* @return void
* @param TNamedObject|TTemplateParam|TIterable|TObjectWithProperties $type
*/
public function addIntersectionType(TNamedObject $type)
public function addIntersectionType(Type\Atomic $type) : void
{
$this->extra_types[$type->getKey()] = $type;
}
/**
* @return array<string, TNamedObject|TTemplateParam|TIterable>|null
* @return array<string, TNamedObject|TTemplateParam|TIterable|TObjectWithProperties>|null
*/
public function getIntersectionTypes()
public function getIntersectionTypes() : ?array
{
return $this->extra_types;
}
/**
* @param array<string, array<string, array{Type\Union, 1?:int}>> $template_types
*
* @return void
*/
public function replaceIntersectionTemplateTypesWithArgTypes(array $template_types, ?Codebase $codebase)
public function replaceIntersectionTemplateTypesWithArgTypes(array $template_types, ?Codebase $codebase) : void
{
if (!$this->extra_types) {
return;

View File

@ -6,6 +6,8 @@ use Psalm\Type\Atomic;
class TObjectWithProperties extends TObject
{
use HasIntersectionTrait;
/**
* @var array<string|int, Union>
*/
@ -23,6 +25,12 @@ class TObjectWithProperties extends TObject
public function __toString()
{
$extra_types = '';
if ($this->extra_types) {
$extra_types = '&' . implode('&', $this->extra_types);
}
return 'object{' .
implode(
', ',
@ -40,11 +48,17 @@ class TObjectWithProperties extends TObject
$this->properties
)
) .
'}';
'}' . $extra_types;
}
public function getId()
{
$extra_types = '';
if ($this->extra_types) {
$extra_types = '&' . implode('&', $this->extra_types);
}
return 'object{' .
implode(
', ',
@ -62,7 +76,7 @@ class TObjectWithProperties extends TObject
$this->properties
)
) .
'}';
'}' . $extra_types;
}
/**

View File

@ -1333,12 +1333,22 @@ class Union
}
if ($atomic_type->extra_types) {
foreach ($template_type->getTypes() as $atomic_template_type) {
foreach ($template_type->getTypes() as $template_type_key => $atomic_template_type) {
if ($atomic_template_type instanceof TNamedObject
|| $atomic_template_type instanceof TTemplateParam
|| $atomic_template_type instanceof TIterable
|| $atomic_template_type instanceof Type\Atomic\TObjectWithProperties
) {
$atomic_template_type->extra_types = $atomic_type->extra_types;
} elseif ($atomic_template_type instanceof Type\Atomic\TObject) {
$first_atomic_type = array_shift($atomic_type->extra_types);
if ($atomic_type->extra_types) {
$first_atomic_type->extra_types = $atomic_type->extra_types;
}
$template_type->removeType($template_type_key);
$template_type->addType($first_atomic_type);
}
}
}

View File

@ -2704,6 +2704,78 @@ class TemplateTest extends TestCase
return new TestPromise(true);
}',
],
'allowTemplatedIntersectionFirst' => [
'<?php
class MockObject
{
public function checkExpectations() : void
{
}
}
/**
* @psalm-template RequestedType
* @psalm-param class-string<RequestedType> $className
* @psalm-return RequestedType&MockObject
* @psalm-suppress MixedInferredReturnType
* @psalm-suppress MixedReturnStatement
*/
function mock(string $className)
{
eval(\'"there be dragons"\');
return $instance;
}
class A {
public function foo() : void {}
}
/**
* @psalm-param class-string $className
*/
function useMock(string $className) : void {
mock($className)->checkExpectations();
}
mock(A::class)->foo();'
],
'allowTemplatedIntersectionSecond' => [
'<?php
class MockObject
{
public function checkExpectations() : void
{
}
}
/**
* @psalm-template RequestedType
* @psalm-param class-string<RequestedType> $className
* @psalm-return MockObject&RequestedType
* @psalm-suppress MixedInferredReturnType
* @psalm-suppress MixedReturnStatement
*/
function mock(string $className)
{
eval(\'"there be dragons"\');
return $instance;
}
class A {
public function foo() : void {}
}
/**
* @psalm-param class-string $className
*/
function useMock(string $className) : void {
mock($className)->checkExpectations();
}
mock(A::class)->foo();'
],
];
}