Skip to content
Permalink
Browse files

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

  • Loading branch information...
muglug committed Jun 19, 2019
1 parent 7155ed4 commit 0246f600f4185388287f1467d1ddceb3affb163a
@@ -580,7 +580,9 @@ public static function handleIterable(
}
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('Shouldn’t get a generic param here');
}
@@ -2823,9 +2823,12 @@ public static function getFunctionIdsFromCallableArg(
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('Shouldn’t get a generic param here');
}
$method_id .= '&' . $extra_type->value . '::' . $method_name_arg->value;
}
}
@@ -452,6 +452,8 @@ private static function isObjectContainedByObject(
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 @@ private static function isObjectContainedByObject(
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;
@@ -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 @@ private static function scrapeTypeProperties(
}
}
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 ?: [],
@@ -395,9 +395,10 @@ function (ParseTree $child_tree) use ($template_type_map) {
$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 @@ public static function intersectUnionTypes(
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 = [];
@@ -930,6 +930,7 @@ public function __clone()
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) {
@@ -8,21 +8,19 @@
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 @@ private function getNamespacedIntersectionTypes(
'&',
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 @@ function (Atomic $extra_type) use (
}
/**
* @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;
@@ -6,6 +6,8 @@
class TObjectWithProperties extends TObject
{
use HasIntersectionTrait;
/**
* @var array<string|int, Union>
*/
@@ -23,6 +25,12 @@ public function __construct(array $properties)
public function __toString()
{
$extra_types = '';
if ($this->extra_types) {
$extra_types = '&' . implode('&', $this->extra_types);
}
return 'object{' .
implode(
', ',
@@ -40,11 +48,17 @@ function ($name, Union $type) {
$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 @@ function ($name, Union $type) {
$this->properties
)
) .
'}';
'}' . $extra_types;
}
/**
@@ -1333,12 +1333,22 @@ public function replaceTemplateTypesWithArgTypes(array $template_types, Codebase
}
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);
}
}
}
@@ -2704,6 +2704,78 @@ function test(): TestPromise {
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();'
],
];
}

0 comments on commit 0246f60

Please sign in to comment.
You can’t perform that action at this time.