1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Add initial support for key-of<T>

Ref #762
This commit is contained in:
Matthew Brown 2019-05-23 23:53:48 -04:00
parent a18a564ad9
commit 574545e149
7 changed files with 107 additions and 9 deletions

View File

@ -567,9 +567,7 @@ class CommentAnalyzer
$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');
}

View File

@ -321,6 +321,8 @@ class ArrayFetchAnalyzer
}
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 @@ class ArrayFetchAnalyzer
break;
}
$type = array_values($type->as->getTypes())[0];
$type = clone array_values($type->as->getTypes())[0];
}
if ($type instanceof TNull) {
@ -479,6 +481,29 @@ class ArrayFetchAnalyzer
$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;
}
}

View File

@ -805,8 +805,10 @@ class TypeAnalyzer
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;
}

View File

@ -1783,9 +1783,11 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
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 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
null,
$this->function_template_types + $this->class_template_types
);
$storage->return_type->setFromDocblock();
if ($storage->signature_return_type) {

View File

@ -1,7 +1,7 @@
<?php
namespace Psalm\Type\Atomic;
class TTemplateKeyOf extends Scalar
class TTemplateKeyOf extends TArrayKey
{
/**
* @var string
@ -64,6 +64,9 @@ class TTemplateKeyOf extends Scalar
return null;
}
/**
* @return false
*/
public function canBeFullyExpressedInPhp()
{
return false;

View File

@ -1281,6 +1281,49 @@ class Union
$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();
}
}
}

View File

@ -2323,6 +2323,30 @@ class TemplateTest extends TestCase
}
}',
],
'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',
]
],
];
}