Skip to content

Commit

Permalink
feat: make value-of<T> capable for template types
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrick-Remy committed Jan 27, 2022
1 parent dff8869 commit 8cd5ccd
Show file tree
Hide file tree
Showing 13 changed files with 361 additions and 154 deletions.
4 changes: 3 additions & 1 deletion docs/running_psalm/plugins/plugins_type_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 0 additions & 2 deletions src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
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;
Expand Down Expand Up @@ -79,7 +78,6 @@ public function __construct()
$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);
Expand Down

This file was deleted.

43 changes: 43 additions & 0 deletions src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
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;
Expand Down Expand Up @@ -355,6 +356,48 @@ public static function isContainedBy(
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()),
Expand Down
36 changes: 24 additions & 12 deletions src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

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;
Expand All @@ -18,6 +19,8 @@
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;
Expand Down Expand Up @@ -267,19 +270,28 @@ public static function isContainedBy(

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;
}
}
}
Expand Down
59 changes: 47 additions & 12 deletions src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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;
Expand All @@ -22,6 +23,8 @@
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;

Expand Down Expand Up @@ -230,19 +233,18 @@ public static function replace(
} 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
Expand Down Expand Up @@ -430,4 +432,37 @@ public static function replace(
)->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;
}
}

0 comments on commit 8cd5ccd

Please sign in to comment.