mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
parent
f592e54a4e
commit
f7a37d05b9
@ -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('Shouldn’t 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'
|
||||
))
|
||||
|
@ -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('Shouldn’t get a generic param here');
|
||||
}
|
||||
|
||||
$method_id = $intersection_type->value . '::' . $method_name_lc;
|
||||
$fq_class_name = $intersection_type->value;
|
||||
|
||||
|
@ -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('Shouldn’t get a generic param here');
|
||||
}
|
||||
$method_id .= '&' . $extra_type->value . '::' . $method_name_arg->value;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
54
src/Psalm/Type/Atomic/HasIntersectionTrait.php
Normal file
54
src/Psalm/Type/Atomic/HasIntersectionTrait.php
Normal 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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -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
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user