1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Improve treatment of key-of

Ref #1698
This commit is contained in:
Matthew Brown 2019-06-07 21:27:50 -04:00
parent 2fa0f27872
commit b0678bdc74
5 changed files with 126 additions and 27 deletions

View File

@ -12,6 +12,7 @@ use Psalm\Internal\Analyzer\ProjectAnalyzer;
use Psalm\Internal\Analyzer\ScopeAnalyzer;
use Psalm\Internal\Analyzer\SourceAnalyzer;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Analyzer\TypeAnalyzer;
use Psalm\CodeLocation;
@ -631,6 +632,8 @@ class ReturnTypeAnalyzer
$parent_class = null;
$classlike_storage = null;
if ($context->self) {
$classlike_storage = $codebase->classlike_storage_provider->get($context->self);
$parent_class = $classlike_storage->parent_class;
@ -686,6 +689,15 @@ class ReturnTypeAnalyzer
$parent_class
);
if ($classlike_storage && $classlike_storage->template_types && $context->self) {
$generic_params = [];
$fleshed_out_return_type->replaceTemplateTypesWithStandins(
$classlike_storage->template_types,
$generic_params,
$codebase
);
}
if (!TypeAnalyzer::isContainedBy(
$codebase,
$fleshed_out_return_type,

View File

@ -444,7 +444,32 @@ class ArrayFetchAnalyzer
? new Type\Union([ new TArrayKey ])
: $type->type_params[0];
if ((!TypeAnalyzer::isContainedBy(
$templated_offset_type = null;
foreach ($offset_type->getTypes() as $offset_atomic_type) {
if ($offset_atomic_type instanceof TTemplateParam) {
$templated_offset_type = $offset_atomic_type;
}
}
if ($original_type instanceof TTemplateParam && $templated_offset_type) {
foreach ($templated_offset_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
) {
$type->type_params[1] = new Type\Union([
new Type\Atomic\TTemplateIndexedAccess(
$offset_as->param_name,
$templated_offset_type->param_name,
$offset_as->defining_class
)
]);
$has_valid_offset = true;
}
}
} elseif ((!TypeAnalyzer::isContainedBy(
$codebase,
$offset_type,
$expected_offset_type,
@ -481,27 +506,6 @@ 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
) {
$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

@ -955,6 +955,8 @@ class Union
$keys_to_unset = [];
foreach ($this->types as $key => $atomic_type) {
$original_key = $key;
if ($bracket_pos = strpos($key, '<')) {
$key = substr($key, 0, $bracket_pos);
}
@ -1016,7 +1018,7 @@ class Union
foreach ($replacement_type->getTypes() as $replacement_key => $_) {
if ($replacement_key !== $key) {
$keys_to_unset[] = $key;
$keys_to_unset[] = $original_key;
}
}
}
@ -1082,7 +1084,7 @@ class Union
$class_string = new Type\Atomic\TClassString($atomic_type->as, $atomic_type->as_type);
$keys_to_unset[] = $key;
$keys_to_unset[] = $original_key;
$this->types[$class_string->getKey()] = $class_string;
@ -1160,10 +1162,37 @@ class Union
$replacement_type
= clone $array_template_type->properties[$offset_template_type->value];
$keys_to_unset[] = $key;
$keys_to_unset[] = $original_key;
foreach ($replacement_type->getTypes() as $replacement_atomic_type) {
$this->types[$replacement_atomic_type->getKey()] = clone $replacement_atomic_type;
$this->types[$replacement_atomic_type->getKey()] = $replacement_atomic_type;
}
}
}
}
}
} elseif ($atomic_type instanceof Type\Atomic\TTemplateKeyOf) {
if ($replace) {
if (isset($template_types[$atomic_type->param_name][$atomic_type->defining_class ?: ''])) {
$template_type
= $template_types[$atomic_type->param_name][$atomic_type->defining_class ?: ''][0];
if ($template_type->isSingle()) {
$template_type = array_values($template_type->types)[0];
if ($template_type instanceof Type\Atomic\ObjectLike
|| $template_type instanceof Type\Atomic\TArray
) {
if ($template_type instanceof Type\Atomic\ObjectLike) {
$key_type = $template_type->getGenericKeyType();
} else {
$key_type = clone $template_type->type_params[0];
}
$keys_to_unset[] = $original_key;
foreach ($key_type->getTypes() as $key_atomic_type) {
$this->types[$key_atomic_type->getKey()] = $key_atomic_type;
}
}
}

View File

@ -1903,7 +1903,7 @@ class TemplateExtendsTest extends TestCase
return [(string)$v => $v];
}
}',
]
],
];
}

View File

@ -2637,6 +2637,60 @@ class TemplateTest extends TestCase
return bar($s);
}'
],
'keyOfArrayGet' => [
'<?php
/**
* @template DATA as array<string, int|bool>
*/
abstract class Foo {
/**
* @var DATA
*/
protected $data;
/**
* @param DATA $data
*/
public function __construct(array $data) {
$this->data = $data;
}
/**
* @template K as key-of<DATA>
*
* @param K $property
*
* @return DATA[K]
*/
public function __get(string $property) {
return $this->data[$property];
}
}',
],
'keyOfArrayRandomKey' => [
'<?php
/**
* @template DATA as array<string, int|bool>
*/
abstract class Foo {
/**
* @var DATA
*/
protected $data;
/**
* @param DATA $data
*/
public function __construct(array $data) {
$this->data = $data;
}
/**
* @return key-of<DATA>
*/
abstract public function getRandomKey() : string;
}'
],
];
}