1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Improve handling of generic params in intersection types

Ref #1053
This commit is contained in:
Brown 2018-11-02 13:08:56 -04:00
parent f592e54a4e
commit f7a37d05b9
8 changed files with 148 additions and 37 deletions

View File

@ -168,7 +168,7 @@ class ForeachChecker
return false;
}
}
if (TypeChecker::isAtomicContainedBy(
$codebase,
$iterator_type,
@ -184,12 +184,16 @@ class ForeachChecker
}
foreach ($iterator_types as $iterator_type) {
if ($iterator_type instanceof Type\Atomic\TGenericParam) {
throw new \UnexpectedValueException('Shouldnt get a generic param here');
}
$has_valid_iterator = true;
if ($iterator_type instanceof Type\Atomic\TGenericObject &&
(strtolower($iterator_type->value) === 'iterable' ||
strtolower($iterator_type->value) === 'traversable' ||
$codebase->classImplements(
if ($iterator_type instanceof Type\Atomic\TGenericObject
&& (strtolower($iterator_type->value) === 'iterable'
|| strtolower($iterator_type->value) === 'traversable'
|| $codebase->classImplements(
$iterator_type->value,
'Traversable'
))

View File

@ -415,6 +415,10 @@ class MethodCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
if ($intersection_types && !$codebase->methodExists($method_id)) {
foreach ($intersection_types as $intersection_type) {
if ($intersection_type instanceof Type\Atomic\TGenericParam) {
throw new \UnexpectedValueException('Shouldnt get a generic param here');
}
$method_id = $intersection_type->value . '::' . $method_name_lc;
$fq_class_name = $intersection_type->value;

View File

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

View File

@ -303,9 +303,17 @@ class TypeChecker
$intersection_container_types[] = $container_type_part;
foreach ($intersection_container_types as $intersection_container_type) {
if ($intersection_container_type instanceof TGenericParam) {
continue;
}
$intersection_container_type_lower = strtolower($intersection_container_type->value);
foreach ($intersection_input_types as $intersection_input_type) {
if ($intersection_input_type instanceof TGenericParam) {
continue;
}
$intersection_input_type_lower = strtolower($intersection_input_type->value);
if ($intersection_container_type_lower === $intersection_input_type_lower) {

View File

@ -13,6 +13,7 @@ use Psalm\Type\Atomic\TEmpty;
use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TGenericObject;
use Psalm\Type\Atomic\TGenericParam;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TLiteralClassString;
use Psalm\Type\Atomic\TLiteralFloat;
@ -271,12 +272,14 @@ abstract class Type
);
foreach ($intersection_types as $intersection_type) {
if (!$intersection_type instanceof TNamedObject) {
if (!$intersection_type instanceof TNamedObject
&& !$intersection_type instanceof TGenericParam
) {
throw new TypeParseTreeException('Intersection types must all be objects');
}
}
/** @var TNamedObject[] $intersection_types */
/** @var array<int, TNamedObject|TGenericParam> $intersection_types */
$first_type = array_shift($intersection_types);
$first_type->extra_types = $intersection_types;

View File

@ -0,0 +1,54 @@
<?php
namespace Psalm\Type\Atomic;
use Psalm\Codebase;
use Psalm\Storage\FunctionLikeParameter;
use Psalm\Type\Atomic;
use Psalm\Type\Union;
trait HasIntersectionTrait
{
/**
* @var array<int, TNamedObject|TGenericParam>|null
*/
public $extra_types;
/**
* @param string|null $namespace
* @param array<string> $aliased_classes
* @param string|null $this_class
* @param bool $use_phpdoc_format
*
* @return string
*/
private function getNamespacedIntersectionTypes($namespace, array $aliased_classes, $this_class, $use_phpdoc_format)
{
if (!$this->extra_types) {
return '';
}
return '&' . implode(
'&',
array_map(
/**
* @param TNamedObject|TGenericParam $extra_type
* @return string
*/
function (Atomic $extra_type) use (
$namespace,
$aliased_classes,
$this_class,
$use_phpdoc_format
) {
return $extra_type->toNamespacedString(
$namespace,
$aliased_classes,
$this_class,
$use_phpdoc_format
);
},
$this->extra_types
)
);
}
}

View File

@ -3,6 +3,8 @@ namespace Psalm\Type\Atomic;
class TGenericParam extends \Psalm\Type\Atomic
{
use HasIntersectionTrait;
/**
* @var string
*/
@ -53,6 +55,26 @@ class TGenericParam extends \Psalm\Type\Atomic
return null;
}
/**
* @param string|null $namespace
* @param array<string> $aliased_classes
* @param string|null $this_class
* @param bool $use_phpdoc_format
*
* @return string
*/
public function toNamespacedString($namespace, array $aliased_classes, $this_class, $use_phpdoc_format)
{
$intersection_types = $this->getNamespacedIntersectionTypes(
$namespace,
$aliased_classes,
$this_class,
$use_phpdoc_format
);
return $this->param_name . $intersection_types;
}
/**
* @return bool
*/

View File

@ -2,19 +2,17 @@
namespace Psalm\Type\Atomic;
use Psalm\Type\Atomic;
use Psalm\Type\Union;
class TNamedObject extends Atomic
{
use HasIntersectionTrait;
/**
* @var string
*/
public $value;
/**
* @var TNamedObject[]|null
*/
public $extra_types;
/**
* @param string $value the name of the object
*/
@ -57,30 +55,12 @@ class TNamedObject extends Atomic
$class_parts = explode('\\', $this->value);
$class_name = array_pop($class_parts);
$intersection_types = $this->extra_types
? '&' . implode(
'&',
array_map(
/**
* @return string
*/
function (TNamedObject $extra_type) use (
$namespace,
$aliased_classes,
$this_class,
$use_phpdoc_format
) {
return $extra_type->toNamespacedString(
$namespace,
$aliased_classes,
$this_class,
$use_phpdoc_format
);
},
$this->extra_types
)
)
: '';
$intersection_types = $this->getNamespacedIntersectionTypes(
$namespace,
$aliased_classes,
$this_class,
$use_phpdoc_format
);
if ($this->value === $this_class) {
return 'self' . $intersection_types;
@ -136,10 +116,43 @@ class TNamedObject extends Atomic
}
/**
* @return TNamedObject[]|null
* @return array<int, TNamedObject|TGenericParam>|null
*/
public function getIntersectionTypes()
{
return $this->extra_types;
}
/**
* @param array<string, Union> $template_types
*
* @return void
*/
public function replaceTemplateTypesWithArgTypes(array $template_types)
{
if (!$this->extra_types) {
return;
}
$keys_to_unset = [];
$new_types = [];
foreach ($this->extra_types as $i => $extra_type) {
if ($extra_type instanceof TGenericParam && isset($template_types[$extra_type->param_name])) {
$keys_to_unset[] = $i;
$template_type = clone $template_types[$extra_type->param_name];
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;
}
}