1
0
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:
Brown 2019-01-10 19:01:30 -05:00
parent ddc2637fc5
commit 0d28d5d82b
7 changed files with 101 additions and 62 deletions

View File

@ -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();

View File

@ -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
);
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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
*/

View File

@ -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];
}
}
}

View File

@ -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',
],
];
}
}