mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
parent
2fa0f27872
commit
b0678bdc74
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1903,7 +1903,7 @@ class TemplateExtendsTest extends TestCase
|
||||
return [(string)$v => $v];
|
||||
}
|
||||
}',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user