mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
Add check for @template-extends return types
This commit is contained in:
parent
ddc2637fc5
commit
0d28d5d82b
@ -534,6 +534,21 @@ class MethodAnalyzer extends FunctionLikeAnalyzer
|
||||
$guide_classlike_storage->name
|
||||
);
|
||||
|
||||
$guide_class_name_lc = strtolower($guide_classlike_storage->name);
|
||||
|
||||
if (isset($implementer_classlike_storage->template_type_extends[$guide_class_name_lc])) {
|
||||
$map = $implementer_classlike_storage->template_type_extends[$guide_class_name_lc];
|
||||
|
||||
$guide_method_storage_return_type->replaceTemplateTypesWithArgTypes(
|
||||
array_map(
|
||||
function (Type\Atomic $u) use ($guide_classlike_storage) : array {
|
||||
return [new Type\Union([$u]), $guide_classlike_storage->name];
|
||||
},
|
||||
$map
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// treat void as null when comparing against docblock implementer
|
||||
if ($implementer_method_storage_return_type->isVoid()) {
|
||||
$implementer_method_storage_return_type = Type::getNull();
|
||||
|
@ -1039,19 +1039,20 @@ class TypeAnalyzer
|
||||
$container_class_lc = strtolower($container_type_part->value);
|
||||
|
||||
// attempt to transform it
|
||||
if ($class_storage->template_value_extends
|
||||
&& isset($class_storage->template_value_extends[$container_class_lc])
|
||||
) {
|
||||
$extends_list = $class_storage->template_value_extends[$container_class_lc];
|
||||
if (isset($class_storage->template_type_extends[$container_class_lc])) {
|
||||
$extends_list = $class_storage->template_type_extends[$container_class_lc];
|
||||
|
||||
$generic_params = [];
|
||||
|
||||
foreach ($extends_list as $key => $value) {
|
||||
if (is_int($key)) {
|
||||
$generic_params[] = new Type\Union([$value]);
|
||||
}
|
||||
}
|
||||
|
||||
$input_type_part = new TGenericObject(
|
||||
$input_type_part->value,
|
||||
array_map(
|
||||
function (Type\Atomic $a) : Type\Union {
|
||||
return new Type\Union([$a]);
|
||||
},
|
||||
$extends_list
|
||||
)
|
||||
$generic_params
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -333,20 +333,17 @@ class Populator
|
||||
|
||||
$storage->parent_classes = array_merge($storage->parent_classes, $parent_storage->parent_classes);
|
||||
|
||||
if (isset($storage->template_type_extends[$parent_storage_class])) {
|
||||
$i = 0;
|
||||
|
||||
foreach ($storage->template_type_extends[$parent_storage_class] as $template_name => $_) {
|
||||
if ($parent_storage->template_types) {
|
||||
if ($parent_storage->template_types
|
||||
&& isset($storage->template_type_extends[$parent_storage_class])
|
||||
) {
|
||||
foreach ($storage->template_type_extends[$parent_storage_class] as $i => $type) {
|
||||
$parent_template_type_names = array_keys($parent_storage->template_types);
|
||||
|
||||
if (isset($parent_template_type_names[$i])) {
|
||||
$storage->template_type_extends[$parent_storage_class][$template_name]
|
||||
= $parent_template_type_names[$i];
|
||||
}
|
||||
}
|
||||
$mapped_name = $parent_template_type_names[$i] ?? null;
|
||||
|
||||
$i++;
|
||||
if ($mapped_name) {
|
||||
$storage->template_type_extends[$parent_storage_class][$mapped_name] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -479,20 +476,17 @@ class Populator
|
||||
$implemented_interface_storage->invalid_dependencies
|
||||
);
|
||||
|
||||
if (isset($storage->template_type_extends[$implemented_interface_lc])) {
|
||||
$i = 0;
|
||||
|
||||
if ($implemented_interface_storage->template_types) {
|
||||
foreach ($storage->template_type_extends[$implemented_interface_lc] as $template_name => $_) {
|
||||
if ($implemented_interface_storage->template_types
|
||||
&& isset($storage->template_type_extends[$implemented_interface_lc])
|
||||
) {
|
||||
foreach ($storage->template_type_extends[$implemented_interface_lc] as $i => $type) {
|
||||
$parent_template_type_names = array_keys($implemented_interface_storage->template_types);
|
||||
|
||||
if (isset($parent_template_type_names[$i])) {
|
||||
$storage->template_type_extends[$implemented_interface_lc][$template_name]
|
||||
= $parent_template_type_names[$i];
|
||||
}
|
||||
}
|
||||
$mapped_name = $parent_template_type_names[$i] ?? null;
|
||||
|
||||
$i++;
|
||||
if ($mapped_name) {
|
||||
$storage->template_type_extends[$implemented_interface_lc][$mapped_name] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -885,7 +885,6 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
}
|
||||
|
||||
$extended_type_parameters = [];
|
||||
$extended_type_values = [];
|
||||
|
||||
foreach ($atomic_type->type_params as $i => $type_param) {
|
||||
if (!$type_param->isSingle()) {
|
||||
@ -900,21 +899,13 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
}
|
||||
|
||||
foreach ($type_param->getTypes() as $type_param_atomic) {
|
||||
if ($type_param_atomic instanceof Type\Atomic\TGenericParam) {
|
||||
$extended_type_parameters[$type_param_atomic->param_name] = null;
|
||||
} else {
|
||||
$extended_type_values[$i] = $type_param_atomic;
|
||||
}
|
||||
$extended_type_parameters[] = $type_param_atomic;
|
||||
}
|
||||
}
|
||||
|
||||
if ($extended_type_parameters) {
|
||||
$storage->template_type_extends[$generic_class_lc] = $extended_type_parameters;
|
||||
}
|
||||
|
||||
if ($extended_type_values) {
|
||||
$storage->template_value_extends[$generic_class_lc] = $extended_type_values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,15 +266,10 @@ class ClassLikeStorage
|
||||
public $template_types;
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, ?string>>|null
|
||||
* @var array<string, array<int|string, Type\Atomic>>|null
|
||||
*/
|
||||
public $template_type_extends;
|
||||
|
||||
/**
|
||||
* @var array<string, array<int, Type\Atomic>>|null
|
||||
*/
|
||||
public $template_value_extends;
|
||||
|
||||
/**
|
||||
* @var array<string, array<int, CodeLocation>>|null
|
||||
*/
|
||||
|
@ -11,6 +11,7 @@ use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TLiteralFloat;
|
||||
use Psalm\Type\Atomic\TLiteralInt;
|
||||
use Psalm\Type\Atomic\TLiteralString;
|
||||
use Psalm\Type\Atomic\TGenericObject;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Internal\Type\TypeCombination;
|
||||
@ -876,22 +877,27 @@ class Union
|
||||
$classlike_storage =
|
||||
$codebase->classlike_storage_provider->get($atomic_input_type->value);
|
||||
|
||||
if (isset($classlike_storage->template_type_extends[strtolower($key)])) {
|
||||
if ($atomic_input_type instanceof TGenericObject
|
||||
&& isset($classlike_storage->template_type_extends[strtolower($key)])
|
||||
) {
|
||||
$matching_atomic_type = $atomic_input_type;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($classlike_storage->template_value_extends[strtolower($key)])) {
|
||||
$extends_list = $classlike_storage->template_value_extends[strtolower($key)];
|
||||
if (isset($classlike_storage->template_type_extends[strtolower($key)])) {
|
||||
$extends_list = $classlike_storage->template_type_extends[strtolower($key)];
|
||||
|
||||
$matching_atomic_type = new Type\Atomic\TGenericObject(
|
||||
$generic_params = [];
|
||||
|
||||
foreach ($extends_list as $key => $value) {
|
||||
if (is_int($key)) {
|
||||
$generic_params[] = new Type\Union([$value]);
|
||||
}
|
||||
}
|
||||
|
||||
$matching_atomic_type = new TGenericObject(
|
||||
$atomic_input_type->value,
|
||||
array_map(
|
||||
function (Type\Atomic $a) : Type\Union {
|
||||
return new Type\Union([$a]);
|
||||
},
|
||||
$extends_list
|
||||
)
|
||||
$generic_params
|
||||
);
|
||||
break;
|
||||
}
|
||||
@ -955,12 +961,11 @@ class Union
|
||||
|
||||
if ($classlike_storage->template_type_extends) {
|
||||
foreach ($classlike_storage->template_type_extends as $fq_class_name_lc => $param_map) {
|
||||
$param_map_reversed = array_flip($param_map);
|
||||
if (strtolower($atomic_type->defining_class) === $fq_class_name_lc
|
||||
&& isset($param_map_reversed[$key])
|
||||
&& isset($template_types[$param_map_reversed[$key]])
|
||||
&& isset($param_map[$key])
|
||||
&& isset($template_types[(string) $param_map[$key]])
|
||||
) {
|
||||
$template_type = clone $template_types[$param_map_reversed[$key]][0];
|
||||
$template_type = clone $template_types[(string) $param_map[$key]][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1878,6 +1878,44 @@ class TemplateTest extends TestCase
|
||||
apply(function(AChild $_i) : void {}, new A());',
|
||||
'error_message' => 'TypeCoercion',
|
||||
],
|
||||
'extendsWithUnfulfilledNonTemplate' => [
|
||||
'<?php
|
||||
namespace A;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
abstract class Container
|
||||
{
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
public abstract function getItem();
|
||||
}
|
||||
|
||||
class Foo
|
||||
{
|
||||
}
|
||||
|
||||
class Bar
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @template-extends Container<Bar>
|
||||
*/
|
||||
class BarContainer extends Container
|
||||
{
|
||||
/**
|
||||
* @return Foo
|
||||
*/
|
||||
public function getItem()
|
||||
{
|
||||
return new Foo();
|
||||
}
|
||||
}',
|
||||
'error_message' => 'ImplementedReturnTypeMismatch',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user