mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
feat: make value-of<T>
capable for template types
This commit is contained in:
parent
dff8869685
commit
8cd5ccd076
@ -55,7 +55,9 @@ The classes are as follows:
|
||||
|
||||
`TTemplateIndexedAccess` - To be documented
|
||||
|
||||
`TTemplateKeyOf` - Represents the type used when using TKeyOfClassConstant when the type of the class constant array is a template
|
||||
`TTemplateKeyOf` - Represents the type used when using TKeyOfArray when the type of the array is a template
|
||||
|
||||
`TTemplateValueOf` - Represents the type used when using TValueOfArray when the type of the array is a template
|
||||
|
||||
`TTypeAlias` - To be documented
|
||||
|
||||
|
@ -21,7 +21,6 @@ use Psalm\Internal\Provider\ReturnTypeProvider\ArrayReverseReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\ArraySliceReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\ArraySpliceReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\ArrayUniqueReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\ArrayValuesReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\ExplodeReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\FilterVarReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\FirstArgStringReturnTypeProvider;
|
||||
@ -79,7 +78,6 @@ class FunctionReturnTypeProvider
|
||||
$this->registerClass(ArraySpliceReturnTypeProvider::class);
|
||||
$this->registerClass(ArrayReverseReturnTypeProvider::class);
|
||||
$this->registerClass(ArrayUniqueReturnTypeProvider::class);
|
||||
$this->registerClass(ArrayValuesReturnTypeProvider::class);
|
||||
$this->registerClass(ArrayFillReturnTypeProvider::class);
|
||||
$this->registerClass(FilterVarReturnTypeProvider::class);
|
||||
$this->registerClass(IteratorToArrayReturnTypeProvider::class);
|
||||
|
@ -1,91 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Internal\Provider\ReturnTypeProvider;
|
||||
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
|
||||
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TKeyedArray;
|
||||
use Psalm\Type\Atomic\TList;
|
||||
use Psalm\Type\Atomic\TNonEmptyArray;
|
||||
use Psalm\Type\Atomic\TNonEmptyList;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Union;
|
||||
use UnexpectedValueException;
|
||||
|
||||
use function array_merge;
|
||||
use function array_shift;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ArrayValuesReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
{
|
||||
/**
|
||||
* @return array<lowercase-string>
|
||||
*/
|
||||
public static function getFunctionIds(): array
|
||||
{
|
||||
return ['array_values'];
|
||||
}
|
||||
|
||||
public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): Union
|
||||
{
|
||||
$statements_source = $event->getStatementsSource();
|
||||
$call_args = $event->getCallArgs();
|
||||
if (!$statements_source instanceof StatementsAnalyzer) {
|
||||
return Type::getMixed();
|
||||
}
|
||||
|
||||
$first_arg = $call_args[0]->value ?? null;
|
||||
|
||||
if (!$first_arg) {
|
||||
return Type::getArray();
|
||||
}
|
||||
|
||||
$first_arg_type = $statements_source->node_data->getType($first_arg);
|
||||
|
||||
if (!$first_arg_type) {
|
||||
return Type::getArray();
|
||||
}
|
||||
|
||||
$atomic_types = $first_arg_type->getAtomicTypes();
|
||||
|
||||
$return_atomic_type = null;
|
||||
|
||||
while ($atomic_type = array_shift($atomic_types)) {
|
||||
if ($atomic_type instanceof TTemplateParam) {
|
||||
$atomic_types = array_merge($atomic_types, $atomic_type->as->getAtomicTypes());
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($atomic_type instanceof TKeyedArray) {
|
||||
$atomic_type = $atomic_type->getGenericArrayType();
|
||||
}
|
||||
|
||||
if ($atomic_type instanceof TArray) {
|
||||
if ($atomic_type instanceof TNonEmptyArray) {
|
||||
$return_atomic_type = new TNonEmptyList(
|
||||
clone $atomic_type->type_params[1]
|
||||
);
|
||||
} else {
|
||||
$return_atomic_type = new TList(
|
||||
clone $atomic_type->type_params[1]
|
||||
);
|
||||
}
|
||||
} elseif ($atomic_type instanceof TList) {
|
||||
$return_atomic_type = $atomic_type;
|
||||
} else {
|
||||
return Type::getArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$return_atomic_type) {
|
||||
throw new UnexpectedValueException('This should never happen');
|
||||
}
|
||||
|
||||
return new Union([$return_atomic_type]);
|
||||
}
|
||||
}
|
@ -33,6 +33,7 @@ use Psalm\Type\Atomic\TObjectWithProperties;
|
||||
use Psalm\Type\Atomic\TScalar;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\TTemplateValueOf;
|
||||
|
||||
use function array_merge;
|
||||
use function array_values;
|
||||
@ -355,6 +356,48 @@ class AtomicTypeComparator
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($container_type_part instanceof TTemplateValueOf) {
|
||||
if (!$input_type_part instanceof TTemplateValueOf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
$input_type_part->as,
|
||||
$container_type_part->as
|
||||
);
|
||||
}
|
||||
|
||||
if ($input_type_part instanceof TTemplateValueOf) {
|
||||
foreach ($input_type_part->as->getAtomicTypes() as $atomic_type) {
|
||||
/** @var TArray|TList|TKeyedArray $atomic_type */
|
||||
|
||||
// Transform all types to TArray if needed
|
||||
if ($atomic_type instanceof TArray) {
|
||||
$array_value_atomics = $atomic_type->type_params[1];
|
||||
} elseif ($atomic_type instanceof TList) {
|
||||
$array_value_atomics = $atomic_type->type_param;
|
||||
} else {
|
||||
$array_value_atomics = $atomic_type->getGenericValueType();
|
||||
}
|
||||
|
||||
foreach ($array_value_atomics->getAtomicTypes() as $array_value_atomic) {
|
||||
if (!self::isContainedBy(
|
||||
$codebase,
|
||||
$array_value_atomic,
|
||||
$container_type_part,
|
||||
$allow_interface_equality,
|
||||
$allow_float_int_equality,
|
||||
$atomic_comparison_result
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($container_type_part instanceof TConditional) {
|
||||
$atomic_types = array_merge(
|
||||
array_values($container_type_part->if_type->getAtomicTypes()),
|
||||
|
@ -4,6 +4,7 @@ namespace Psalm\Internal\Type\Comparator;
|
||||
|
||||
use Psalm\Codebase;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\Scalar;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TArrayKey;
|
||||
@ -18,6 +19,8 @@ use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TIntRange;
|
||||
use Psalm\Type\Atomic\TKeyedArray;
|
||||
use Psalm\Type\Atomic\TList;
|
||||
use Psalm\Type\Atomic\TLiteralClassString;
|
||||
use Psalm\Type\Atomic\TLiteralFloat;
|
||||
use Psalm\Type\Atomic\TLiteralInt;
|
||||
@ -267,19 +270,28 @@ class ScalarTypeComparator
|
||||
|
||||
if ($input_type_part instanceof TTemplateKeyOf) {
|
||||
foreach ($input_type_part->as->getAtomicTypes() as $atomic_type) {
|
||||
/** @var TArray|TList|TKeyedArray $atomic_type */
|
||||
|
||||
// Transform all types to TArray if needed
|
||||
if ($atomic_type instanceof TArray) {
|
||||
/** @var Scalar $array_key_atomic */
|
||||
foreach ($atomic_type->type_params[0]->getAtomicTypes() as $array_key_atomic) {
|
||||
if (!self::isContainedBy(
|
||||
$codebase,
|
||||
$array_key_atomic,
|
||||
$container_type_part,
|
||||
$allow_interface_equality,
|
||||
$allow_float_int_equality,
|
||||
$atomic_comparison_result
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
$array_key_atomics = $atomic_type->type_params[0];
|
||||
} elseif ($atomic_type instanceof TList) {
|
||||
$array_key_atomics = Type::getInt();
|
||||
} else {
|
||||
$array_key_atomics = $atomic_type->getGenericKeyType();
|
||||
}
|
||||
|
||||
/** @var Scalar $array_key_atomic */
|
||||
foreach ($array_key_atomics->getAtomicTypes() as $array_key_atomic) {
|
||||
if (!self::isContainedBy(
|
||||
$codebase,
|
||||
$array_key_atomic,
|
||||
$container_type_part,
|
||||
$allow_interface_equality,
|
||||
$allow_float_int_equality,
|
||||
$atomic_comparison_result
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use InvalidArgumentException;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic;
|
||||
use Psalm\Type\Atomic\TClassString;
|
||||
use Psalm\Type\Atomic\TConditional;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
@ -22,6 +23,8 @@ use Psalm\Type\Atomic\TTemplateIndexedAccess;
|
||||
use Psalm\Type\Atomic\TTemplateKeyOf;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\TTemplateParamClass;
|
||||
use Psalm\Type\Atomic\TTemplateValueOf;
|
||||
use Psalm\Type\Atomic\TValueOfArray;
|
||||
use Psalm\Type\Union;
|
||||
use UnexpectedValueException;
|
||||
|
||||
@ -230,19 +233,18 @@ class TemplateInferredTypeReplacer
|
||||
} else {
|
||||
$new_types[] = new TMixed();
|
||||
}
|
||||
} elseif ($atomic_type instanceof TTemplateKeyOf) {
|
||||
$template_type = isset($inferred_lower_bounds[$atomic_type->param_name][$atomic_type->defining_class])
|
||||
? clone TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds(
|
||||
$inferred_lower_bounds[$atomic_type->param_name][$atomic_type->defining_class],
|
||||
$codebase
|
||||
)
|
||||
: null;
|
||||
} elseif ($atomic_type instanceof TTemplateKeyOf
|
||||
|| $atomic_type instanceof TTemplateValueOf
|
||||
) {
|
||||
$new_type = self::replaceTemplateKeyOfValueOf(
|
||||
$codebase,
|
||||
$atomic_type,
|
||||
$inferred_lower_bounds
|
||||
);
|
||||
|
||||
if ($template_type) {
|
||||
if (TKeyOfArray::isViableTemplateType($template_type)) {
|
||||
$keys_to_unset[] = $key;
|
||||
$new_types[] = new TKeyOfArray(clone $template_type);
|
||||
}
|
||||
if ($new_type) {
|
||||
$keys_to_unset[] = $key;
|
||||
$new_types[] = $new_type;
|
||||
}
|
||||
} elseif ($atomic_type instanceof TConditional
|
||||
&& $codebase
|
||||
@ -430,4 +432,37 @@ class TemplateInferredTypeReplacer
|
||||
)->getAtomicTypes()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TTemplateKeyOf|TTemplateValueOf $atomic_type
|
||||
* @param array<string, array<string, non-empty-list<TemplateBound>>> $inferred_lower_bounds
|
||||
*/
|
||||
private static function replaceTemplateKeyOfValueOf(
|
||||
?Codebase $codebase,
|
||||
Atomic $atomic_type,
|
||||
array $inferred_lower_bounds
|
||||
): ?Atomic {
|
||||
if (!isset($inferred_lower_bounds[$atomic_type->param_name][$atomic_type->defining_class])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$template_type = clone TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds(
|
||||
$inferred_lower_bounds[$atomic_type->param_name][$atomic_type->defining_class],
|
||||
$codebase
|
||||
);
|
||||
|
||||
if ($atomic_type instanceof TTemplateKeyOf
|
||||
&& TKeyOfArray::isViableTemplateType($template_type)
|
||||
) {
|
||||
return new TKeyOfArray(clone $template_type);
|
||||
}
|
||||
|
||||
if ($atomic_type instanceof TTemplateValueOf
|
||||
&& TValueOfArray::isViableTemplateType($template_type)
|
||||
) {
|
||||
return new TValueOfArray(clone $template_type);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ use Psalm\Type\Atomic\TTemplateIndexedAccess;
|
||||
use Psalm\Type\Atomic\TTemplateKeyOf;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\TTemplateParamClass;
|
||||
use Psalm\Type\Atomic\TTemplateValueOf;
|
||||
use Psalm\Type\Union;
|
||||
use Throwable;
|
||||
|
||||
@ -270,48 +271,58 @@ class TemplateStandinTypeReplacer
|
||||
return [$atomic_type];
|
||||
}
|
||||
|
||||
if ($atomic_type instanceof TTemplateKeyOf) {
|
||||
if ($replace) {
|
||||
$atomic_types = [];
|
||||
|
||||
$include_first = true;
|
||||
|
||||
if (isset($template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class])) {
|
||||
$template_type
|
||||
= $template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class];
|
||||
|
||||
if ($template_type->isSingle()) {
|
||||
$template_type = $template_type->getSingleAtomic();
|
||||
|
||||
if ($template_type instanceof TKeyedArray
|
||||
|| $template_type instanceof TArray
|
||||
|| $template_type instanceof TList
|
||||
) {
|
||||
if ($template_type instanceof TKeyedArray) {
|
||||
$key_type = $template_type->getGenericKeyType();
|
||||
} elseif ($template_type instanceof TList) {
|
||||
$key_type = Type::getInt();
|
||||
} else {
|
||||
$key_type = clone $template_type->type_params[0];
|
||||
}
|
||||
|
||||
$include_first = false;
|
||||
|
||||
foreach ($key_type->getAtomicTypes() as $key_atomic_type) {
|
||||
$atomic_types[] = $key_atomic_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($include_first) {
|
||||
$atomic_types[] = $atomic_type;
|
||||
}
|
||||
|
||||
return $atomic_types;
|
||||
if ($atomic_type instanceof TTemplateKeyOf
|
||||
|| $atomic_type instanceof TTemplateValueOf) {
|
||||
if (!$replace) {
|
||||
return [$atomic_type];
|
||||
}
|
||||
|
||||
return [$atomic_type];
|
||||
$atomic_types = [];
|
||||
|
||||
$include_first = true;
|
||||
|
||||
if (isset($template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class])) {
|
||||
$template_type = $template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class];
|
||||
|
||||
foreach ($template_type->getAtomicTypes() as $template_atomic) {
|
||||
if (!$template_atomic instanceof TKeyedArray
|
||||
&& !$template_atomic instanceof TArray
|
||||
&& !$template_atomic instanceof TList
|
||||
) {
|
||||
return [$atomic_type];
|
||||
}
|
||||
|
||||
if ($atomic_type instanceof TTemplateKeyOf) {
|
||||
if ($template_atomic instanceof TKeyedArray) {
|
||||
$template_atomic = $template_atomic->getGenericKeyType();
|
||||
} elseif ($template_atomic instanceof TList) {
|
||||
$template_atomic = Type::getInt();
|
||||
} else {
|
||||
$template_atomic = clone $template_atomic->type_params[0];
|
||||
}
|
||||
} else {
|
||||
if ($template_atomic instanceof TKeyedArray) {
|
||||
$template_atomic = $template_atomic->getGenericValueType();
|
||||
} elseif ($template_atomic instanceof TList) {
|
||||
$template_atomic = clone $template_atomic->type_param;
|
||||
} else {
|
||||
$template_atomic = clone $template_atomic->type_params[1];
|
||||
}
|
||||
}
|
||||
|
||||
$include_first = false;
|
||||
|
||||
foreach ($template_atomic->getAtomicTypes() as $key_atomic_type) {
|
||||
$atomic_types[] = $key_atomic_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($include_first) {
|
||||
$atomic_types[] = $atomic_type;
|
||||
}
|
||||
|
||||
return $atomic_types;
|
||||
}
|
||||
|
||||
$matching_atomic_types = [];
|
||||
|
@ -59,12 +59,14 @@ use Psalm\Type\Atomic\TTemplateIndexedAccess;
|
||||
use Psalm\Type\Atomic\TTemplateKeyOf;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\TTemplateParamClass;
|
||||
use Psalm\Type\Atomic\TTemplateValueOf;
|
||||
use Psalm\Type\Atomic\TTypeAlias;
|
||||
use Psalm\Type\Atomic\TValueOfArray;
|
||||
use Psalm\Type\TypeNode;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
use function array_key_exists;
|
||||
use function array_key_first;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
@ -686,9 +688,9 @@ class TypeParser
|
||||
if ($generic_type_value === 'key-of') {
|
||||
$param_name = $generic_params[0]->getId(false);
|
||||
|
||||
if (isset($template_type_map[$param_name])) {
|
||||
$defining_class = array_keys($template_type_map[$param_name])[0];
|
||||
|
||||
if (isset($template_type_map[$param_name])
|
||||
&& ($defining_class = array_key_first($template_type_map[$param_name])) !== null
|
||||
) {
|
||||
return new TTemplateKeyOf(
|
||||
$param_name,
|
||||
$defining_class,
|
||||
@ -698,7 +700,7 @@ class TypeParser
|
||||
|
||||
if (!TKeyOfArray::isViableTemplateType($generic_params[0])) {
|
||||
throw new TypeParseTreeException(
|
||||
'Untemplated key-of param ' . $param_name . ' should be a class constant or an array'
|
||||
'Untemplated key-of param ' . $param_name . ' should be an array'
|
||||
);
|
||||
}
|
||||
|
||||
@ -708,9 +710,19 @@ class TypeParser
|
||||
if ($generic_type_value === 'value-of') {
|
||||
$param_name = $generic_params[0]->getId(false);
|
||||
|
||||
if (isset($template_type_map[$param_name])
|
||||
&& ($defining_class = array_key_first($template_type_map[$param_name])) !== null
|
||||
) {
|
||||
return new TTemplateValueOf(
|
||||
$param_name,
|
||||
$defining_class,
|
||||
$generic_params[0]
|
||||
);
|
||||
}
|
||||
|
||||
if (!TValueOfArray::isViableTemplateType($generic_params[0])) {
|
||||
throw new TypeParseTreeException(
|
||||
'Untemplated value-of param ' . $param_name . ' should be a class constant or an array'
|
||||
'Untemplated value-of param ' . $param_name . ' should be an array'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ namespace Psalm\Type\Atomic;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
/**
|
||||
* Represents the type used when using TKeyOfArray when the type of the class constant array is a template
|
||||
* Represents the type used when using TKeyOfArray when the type of the array is a template
|
||||
*/
|
||||
class TTemplateKeyOf extends TArrayKey
|
||||
{
|
||||
|
80
src/Psalm/Type/Atomic/TTemplateValueOf.php
Normal file
80
src/Psalm/Type/Atomic/TTemplateValueOf.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
use Psalm\Type\Atomic;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
/**
|
||||
* Represents the type used when using TValueOfArray when the type of the array is a template
|
||||
*/
|
||||
class TTemplateValueOf extends Atomic
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $param_name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $defining_class;
|
||||
|
||||
/**
|
||||
* @var Union
|
||||
*/
|
||||
public $as;
|
||||
|
||||
public function __construct(
|
||||
string $param_name,
|
||||
string $defining_class,
|
||||
Union $as
|
||||
) {
|
||||
$this->param_name = $param_name;
|
||||
$this->defining_class = $defining_class;
|
||||
$this->as = $as;
|
||||
}
|
||||
|
||||
public function getKey(bool $include_extra = true): string
|
||||
{
|
||||
return 'value-of<' . $this->param_name . '>';
|
||||
}
|
||||
|
||||
public function getId(bool $exact = true, bool $nested = false): string
|
||||
{
|
||||
if (!$exact) {
|
||||
return 'value-of<' . $this->param_name . '>';
|
||||
}
|
||||
|
||||
return 'value-of<' . $this->param_name . ':' . $this->defining_class . ' as ' . $this->as->getId($exact) . '>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<lowercase-string, string> $aliased_classes
|
||||
*/
|
||||
public function toNamespacedString(
|
||||
?string $namespace,
|
||||
array $aliased_classes,
|
||||
?string $this_class,
|
||||
bool $use_phpdoc_format
|
||||
): string {
|
||||
return 'value-of<' . $this->param_name . '>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<lowercase-string, string> $aliased_classes
|
||||
*/
|
||||
public function toPhpString(
|
||||
?string $namespace,
|
||||
array $aliased_classes,
|
||||
?string $this_class,
|
||||
int $analysis_php_version_id
|
||||
): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
@ -167,6 +167,18 @@ function array_key_last($array)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-template TArray as array
|
||||
*
|
||||
* @param TArray $array
|
||||
*
|
||||
* @return (TArray is non-empty-array ? non-empty-list<value-of<TArray>> : list<value-of<TArray>>)
|
||||
* @psalm-pure
|
||||
*/
|
||||
function array_values($array)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-template T
|
||||
*
|
||||
|
85
tests/Template/ValueOfTemplateTest.php
Normal file
85
tests/Template/ValueOfTemplateTest.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psalm\Tests;
|
||||
|
||||
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
|
||||
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;
|
||||
|
||||
class ValueOfTemplateTest extends TestCase
|
||||
{
|
||||
use InvalidCodeAnalysisTestTrait;
|
||||
use ValidCodeAnalysisTestTrait;
|
||||
|
||||
/**
|
||||
* @return iterable<string,array{code:string,assertions?:array<string,string>,ignored_issues?:list<string>,php_version?:string}>
|
||||
*/
|
||||
public function providerValidCodeParse(): iterable
|
||||
{
|
||||
return [
|
||||
'acceptsArrayValuesFn' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @template T of array
|
||||
* @param T $array
|
||||
* @return value-of<T>[]
|
||||
*/
|
||||
function getValues($array) {
|
||||
return array_values($array);
|
||||
}
|
||||
'
|
||||
],
|
||||
'SKIPPED-acceptsIfInArrayFn' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @template T of array
|
||||
* @param T $array
|
||||
* @return value-of<T>|null
|
||||
*/
|
||||
function getValue(string $value, $array) {
|
||||
if (in_array($value, $array)) {
|
||||
return $value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string,array{code:string,error_message:string,ignored_issues?:list<string>,php_version?:string}>
|
||||
*/
|
||||
public function providerInvalidCodeParse(): iterable
|
||||
{
|
||||
return [
|
||||
'valueOfTemplateNotIncludesString' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @template T of array
|
||||
* @param T $array
|
||||
* @return value-of<T>
|
||||
*/
|
||||
function getValue($array) {
|
||||
return "foo";
|
||||
}
|
||||
',
|
||||
'error_message' => 'InvalidReturnStatement'
|
||||
],
|
||||
'valueOfTemplateNotIncludesInt' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @template T of array
|
||||
* @param T $array
|
||||
* @return value-of<T>
|
||||
*/
|
||||
function getValue($array) {
|
||||
return 0;
|
||||
}
|
||||
',
|
||||
'error_message' => 'InvalidReturnStatement'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -762,6 +762,14 @@ class TypeParseTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testValueOfTemplate(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
'value-of<T>',
|
||||
(string)Type::parseString('value-of<T>', null, ['T' => ['' => Type::getArray()]])
|
||||
);
|
||||
}
|
||||
|
||||
public function testIndexedAccess(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
|
Loading…
x
Reference in New Issue
Block a user