Skip to content
Permalink
Browse files

Fix #1652 - allow totally-templated intersections

  • Loading branch information...
muglug committed May 17, 2019
1 parent 963d5bb commit 4035823d5162ea00b1eaf0b0b1ac23ef9b531a17
@@ -197,5 +197,9 @@ public function replaceTemplateTypesWithArgTypes(array $template_types)
foreach ($this->type_params as $type_param) {
$type_param->replaceTemplateTypesWithArgTypes($template_types);
}
if ($this instanceof TGenericObject || $this instanceof TIterable) {
$this->replaceIntersectionTemplateTypesWithArgTypes($template_types);
}
}
}
@@ -1,6 +1,7 @@
<?php
namespace Psalm\Type\Atomic;
use Psalm\Type;
use Psalm\Type\Atomic;
trait HasIntersectionTrait
@@ -48,4 +49,52 @@ function (Atomic $extra_type) use (
)
);
}
/**
* @param TNamedObject $type
*
* @return void
*/
public function addIntersectionType(TNamedObject $type)
{
$this->extra_types[] = $type;
}
/**
* @return array<int, TNamedObject|TTemplateParam|TIterable>|null
*/
public function getIntersectionTypes()
{
return $this->extra_types;
}
/**
* @param array<string, array<string, array{Type\Union, 1?:int}>> $template_types
*
* @return void
*/
public function replaceIntersectionTemplateTypesWithArgTypes(array $template_types)
{
if (!$this->extra_types) {
return;
}
$new_types = [];
foreach ($this->extra_types as $extra_type) {
if ($extra_type instanceof TTemplateParam && isset($template_types[$extra_type->param_name])) {
$template_type = clone $template_types[$extra_type->param_name][$extra_type->defining_class ?: ''][0];
foreach ($template_type->getTypes() as $template_type_part) {
if ($template_type_part instanceof TNamedObject) {
$new_types[] = $template_type_part;
}
}
} else {
$new_types[] = $extra_type;
}
}
$this->extra_types = $new_types;
}
}
@@ -120,51 +120,13 @@ public function canBeFullyExpressedInPhp()
return $this->value !== 'static';
}
/**
* @param TNamedObject $type
*
* @return void
*/
public function addIntersectionType(TNamedObject $type)
{
$this->extra_types[] = $type;
}
/**
* @return array<int, TNamedObject|TTemplateParam|TIterable>|null
*/
public function getIntersectionTypes()
{
return $this->extra_types;
}
/**
* @param array<string, array<string, array{Type\Union, 1?:int}>> $template_types
*
* @return void
*/
public function replaceTemplateTypesWithArgTypes(array $template_types)
{
if (!$this->extra_types) {
return;
}
$new_types = [];
foreach ($this->extra_types as $extra_type) {
if ($extra_type instanceof TTemplateParam && isset($template_types[$extra_type->param_name])) {
$template_type = clone $template_types[$extra_type->param_name][$extra_type->defining_class ?: ''][0];
foreach ($template_type->getTypes() as $template_type_part) {
if ($template_type_part instanceof TNamedObject) {
$new_types[] = $template_type_part;
}
}
} else {
$new_types[] = $extra_type;
}
}
$this->extra_types = $new_types;
$this->replaceIntersectionTemplateTypesWithArgTypes($template_types);
}
}
@@ -1,6 +1,7 @@
<?php
namespace Psalm\Type\Atomic;
use Psalm\Type;
use Psalm\Type\Union;
class TTemplateParam extends \Psalm\Type\Atomic
@@ -113,4 +114,14 @@ public function canBeFullyExpressedInPhp()
{
return false;
}
/**
* @param array<string, array<string, array{Type\Union, 1?:int}>> $template_types
*
* @return void
*/
public function replaceTemplateTypesWithArgTypes(array $template_types)
{
$this->replaceIntersectionTemplateTypesWithArgTypes($template_types);
}
}
@@ -9,12 +9,14 @@
use Psalm\Type;
use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TIterable;
use Psalm\Type\Atomic\TLiteralFloat;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralString;
use Psalm\Type\Atomic\TGenericObject;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Internal\Type\TypeCombination;
class Union
@@ -1187,19 +1189,32 @@ public function replaceTemplateTypesWithArgTypes(array $template_types, Codebase
$is_mixed = false;
foreach ($this->types as $key => $atomic_type) {
$atomic_type->replaceTemplateTypesWithArgTypes($template_types);
if ($atomic_type instanceof Type\Atomic\TTemplateParam) {
$keys_to_unset[] = $key;
$template_type = null;
if (isset($template_types[$key][$atomic_type->defining_class ?: ''])) {
$template_type = $template_types[$key][$atomic_type->defining_class ?: ''][0];
if (isset($template_types[$atomic_type->param_name][$atomic_type->defining_class ?: ''])) {
$template_type = $template_types[$atomic_type->param_name][$atomic_type->defining_class ?: ''][0];
if (!$atomic_type->as->isMixed() && $template_type->isMixed()) {
$template_type = clone $atomic_type->as;
} else {
$template_type = clone $template_type;
}
if ($atomic_type->extra_types) {
foreach ($template_type->getTypes() as $atomic_template_type) {
if ($atomic_template_type instanceof TNamedObject
|| $atomic_template_type instanceof TTemplateParam
|| $atomic_template_type instanceof TIterable
) {
$atomic_template_type->extra_types = $atomic_type->extra_types;
}
}
}
} elseif ($codebase && $atomic_type->defining_class) {
foreach ($template_types as $template_type_map) {
foreach ($template_type_map as $template_class => $_) {
@@ -1266,8 +1281,6 @@ public function replaceTemplateTypesWithArgTypes(array $template_types, Codebase
$keys_to_unset[] = $key;
}
}
} else {
$atomic_type->replaceTemplateTypesWithArgTypes($template_types);
}
}
@@ -2201,6 +2201,35 @@ function foo(Traversable $t): void {
}
}',
],
'templateArrayIntersection' => [
'<?php
/**
* @template T as object
* @template S as object
* @param array<T> $a
* @param class-string<S> $type
* @return array<T&S>
*/
function filter(array $a, string $type): array {
$result = [];
foreach ($a as $item) {
if (is_a($item, $type)) {
$result[] = $item;
}
}
return $result;
}
interface A {}
interface B {}
/** @var array<A> */
$x = [];
$y = filter($x, B::class);',
[
'$y' => 'array<array-key, A&B>',
]
],
];
}

0 comments on commit 4035823

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