Skip to content
Permalink
Browse files

Add initial support for key-of<T>

Ref #762
  • Loading branch information...
muglug committed May 24, 2019
1 parent a18a564 commit 574545e149aac3e226fb94de0cbc606de9a092fa
@@ -567,9 +567,7 @@ private static function extractReturnType(string $return_block, int $line_number
$line_parts = self::splitDocLine($return_block);
if (!preg_match('/\[[^\]]+\]/', $line_parts[0])
&& $line_parts[0][0] !== '{'
) {
if ($line_parts[0][0] !== '{') {
if ($line_parts[0][0] === '$' && !preg_match('/^\$this(\||$)/', $line_parts[0])) {
throw new IncorrectDocblockException('Misplaced variable');
}
@@ -321,6 +321,8 @@ public static function getArrayAccessTypeGivenOffset(
}
foreach ($array_type->getTypes() as $type_string => $type) {
$original_type = $type;
if ($type instanceof TMixed || $type instanceof TTemplateParam || $type instanceof TEmpty) {
if (!$type instanceof TTemplateParam || $type->as->isMixed() || !$type->as->isSingle()) {
if (!$context->collect_initializations
@@ -362,7 +364,7 @@ public static function getArrayAccessTypeGivenOffset(
break;
}
$type = array_values($type->as->getTypes())[0];
$type = clone array_values($type->as->getTypes())[0];
}
if ($type instanceof TNull) {
@@ -479,6 +481,29 @@ public static function getArrayAccessTypeGivenOffset(
$has_valid_offset = true;
}
} else {
if ($original_type instanceof TTemplateParam) {
foreach ($offset_type->getTypes() as $offset_atomic_type) {
if ($offset_atomic_type instanceof TTemplateParam) {
foreach ($offset_atomic_type->as->getTypes() as $offset_as) {
if ($offset_as instanceof Type\Atomic\TTemplateKeyOf
&& $offset_as->param_name === $original_type->param_name
&& $offset_as->defining_class === $original_type->defining_class
&& $offset_atomic_type->defining_class
=== $original_type->defining_class
) {
$type->type_params[1] = new Type\Union([
new Type\Atomic\TTemplateIndexedAccess(
$offset_as->param_name,
$offset_atomic_type->param_name,
$offset_as->defining_class
)
]);
}
}
}
}
}
$has_valid_offset = true;
}
}
@@ -805,8 +805,10 @@ public static function isAtomicContainedBy(
return true;
}
if ($container_type_part instanceof TArrayKey &&
($input_type_part instanceof TInt || $input_type_part instanceof TString)
if ($container_type_part instanceof TArrayKey
&& ($input_type_part instanceof TInt
|| $input_type_part instanceof TString
|| $input_type_part instanceof Type\Atomic\TTemplateKeyOf)
) {
return true;
}
@@ -1783,9 +1783,11 @@ private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, $fake_m
Type::fixUpLocalType(
$template_map[2],
$this->aliases,
null,
$storage->template_types,
$this->type_aliases
)
),
null,
$storage->template_types
);
} else {
if (IssueBuffer::accepts(
@@ -1952,6 +1954,7 @@ private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, $fake_m
null,
$this->function_template_types + $this->class_template_types
);
$storage->return_type->setFromDocblock();
if ($storage->signature_return_type) {
@@ -1,7 +1,7 @@
<?php
namespace Psalm\Type\Atomic;
class TTemplateKeyOf extends Scalar
class TTemplateKeyOf extends TArrayKey
{
/**
* @var string
@@ -64,6 +64,9 @@ public function toPhpString(
return null;
}
/**
* @return false
*/
public function canBeFullyExpressedInPhp()
{
return false;
@@ -1281,6 +1281,49 @@ public function replaceTemplateTypesWithArgTypes(array $template_types, Codebase
$keys_to_unset[] = $key;
}
}
} elseif ($atomic_type instanceof Type\Atomic\TTemplateIndexedAccess) {
$keys_to_unset[] = $key;
$template_type = null;
if (isset($template_types[$atomic_type->array_param_name][$atomic_type->defining_class ?: ''])
&& isset($template_types[$atomic_type->offset_param_name][$atomic_type->defining_class ?: ''])
) {
$array_template_type
= $template_types[$atomic_type->array_param_name][$atomic_type->defining_class ?: ''][0];
$offset_template_type
= $template_types[$atomic_type->offset_param_name][$atomic_type->defining_class ?: ''][0];
if ($array_template_type->isSingle()
&& $offset_template_type->isSingle()
&& !$array_template_type->isMixed()
&& !$offset_template_type->isMixed()
) {
$array_template_type = array_values($array_template_type->types)[0];
$offset_template_type = array_values($offset_template_type->types)[0];
if ($array_template_type instanceof Type\Atomic\ObjectLike
&& ($offset_template_type instanceof Type\Atomic\TLiteralString
|| $offset_template_type instanceof Type\Atomic\TLiteralInt)
&& isset($array_template_type->properties[$offset_template_type->value])
) {
$template_type = clone $array_template_type->properties[$offset_template_type->value];
}
}
}
if ($template_type) {
foreach ($template_type->types as $template_type_part) {
if ($template_type_part instanceof Type\Atomic\TMixed) {
$is_mixed = true;
}
$new_types[$template_type_part->getKey()] = $template_type_part;
}
} else {
$new_types[$key] = new Type\Atomic\TMixed();
}
}
}
@@ -2323,6 +2323,30 @@ public function __set(string $property, $value) {
}
}',
],
'keyOfTemplate' => [
'<?php
/**
* @template T as array
* @template K as key-of<T>
*
* @param T $o
* @param K $name
*
* @return T[K]
*/
function getOffset(array $o, $name) {
return $o[$name];
}
$a = ["foo" => "hello", "bar" => 2];
$b = getOffset($a, "foo");
$c = getOffset($a, "bar");',
[
'$b' => 'string',
'$c' => 'int',
]
],
];
}

0 comments on commit 574545e

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