diff --git a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php index 95b36b029..caeaa7fb1 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php @@ -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, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index bedec41c8..0ae8d62db 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -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; } } diff --git a/src/Psalm/Type/Union.php b/src/Psalm/Type/Union.php index 30fd6a724..59f491f3c 100644 --- a/src/Psalm/Type/Union.php +++ b/src/Psalm/Type/Union.php @@ -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; } } } diff --git a/tests/Template/TemplateExtendsTest.php b/tests/Template/TemplateExtendsTest.php index 371032b2e..d3e632a59 100644 --- a/tests/Template/TemplateExtendsTest.php +++ b/tests/Template/TemplateExtendsTest.php @@ -1903,7 +1903,7 @@ class TemplateExtendsTest extends TestCase return [(string)$v => $v]; } }', - ] + ], ]; } diff --git a/tests/Template/TemplateTest.php b/tests/Template/TemplateTest.php index c543ef5e2..d2b8e7197 100644 --- a/tests/Template/TemplateTest.php +++ b/tests/Template/TemplateTest.php @@ -2637,6 +2637,60 @@ class TemplateTest extends TestCase return bar($s); }' ], + 'keyOfArrayGet' => [ + ' + */ + abstract class Foo { + /** + * @var DATA + */ + protected $data; + + /** + * @param DATA $data + */ + public function __construct(array $data) { + $this->data = $data; + } + + /** + * @template K as key-of + * + * @param K $property + * + * @return DATA[K] + */ + public function __get(string $property) { + return $this->data[$property]; + } + }', + ], + 'keyOfArrayRandomKey' => [ + ' + */ + abstract class Foo { + /** + * @var DATA + */ + protected $data; + + /** + * @param DATA $data + */ + public function __construct(array $data) { + $this->data = $data; + } + + /** + * @return key-of + */ + abstract public function getRandomKey() : string; + }' + ], ]; }