1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Fix #3137 - support nested templates in class definitions

This commit is contained in:
Brown 2020-04-17 01:16:15 -04:00
parent 1309eece2e
commit 5ef82b53f6
3 changed files with 105 additions and 17 deletions

View File

@ -5,6 +5,7 @@ use PhpParser;
use Psalm\Aliases;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector;
use Psalm\Internal\Type\UnionTemplateHandler;
use Psalm\Codebase;
use Psalm\CodeLocation;
use Psalm\Config;
@ -1050,7 +1051,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
);
if ($class_template_params) {
$fleshed_out_type = \Psalm\Internal\Type\UnionTemplateHandler::replaceTemplateTypesWithStandins(
$fleshed_out_type = UnionTemplateHandler::replaceTemplateTypesWithStandins(
$fleshed_out_type,
$template_result,
$codebase,
@ -1862,7 +1863,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
[]
);
$return_type = \Psalm\Internal\Type\UnionTemplateHandler::replaceTemplateTypesWithStandins(
$return_type = UnionTemplateHandler::replaceTemplateTypesWithStandins(
$return_type,
$template_result,
$codebase,
@ -1970,8 +1971,10 @@ class ClassAnalyzer extends ClassLikeAnalyzer
if ($parent_storage->template_types && $storage->template_type_extends) {
$i = 0;
$previous_extended = [];
foreach ($parent_storage->template_types as $template_name => $type_map) {
foreach ($type_map as $template_type) {
foreach ($type_map as $declaring_class => $template_type) {
if (isset($storage->template_type_extends[$parent_storage->name][$template_name])) {
$extended_type = $storage->template_type_extends[$parent_storage->name][$template_name];
@ -2001,19 +2004,40 @@ class ClassAnalyzer extends ClassLikeAnalyzer
}
}
if (!$template_type[0]->isMixed()
&& !TypeAnalyzer::isContainedBy($codebase, $extended_type, $template_type[0])
) {
if (IssueBuffer::accepts(
new InvalidTemplateParam(
'Extended template param ' . $template_name
. ' expects type ' . $template_type[0]->getId()
. ', type ' . $extended_type->getId() . ' given',
$code_location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
)) {
// fall through
if (!$template_type[0]->isMixed()) {
$template_type_copy = clone $template_type[0];
$template_result = new \Psalm\Internal\Type\TemplateResult(
$previous_extended ?: [],
[]
);
$template_type_copy = UnionTemplateHandler::replaceTemplateTypesWithStandins(
$template_type_copy,
$template_result,
$codebase,
null,
$extended_type,
null,
null
);
if (!TypeAnalyzer::isContainedBy($codebase, $extended_type, $template_type_copy)) {
if (IssueBuffer::accepts(
new InvalidTemplateParam(
'Extended template param ' . $template_name
. ' expects type ' . $template_type[0]->getId()
. ', type ' . $extended_type->getId() . ' given',
$code_location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
)) {
// fall through
}
} else {
$previous_extended[$template_name] = [
$declaring_class => [$extended_type]
];
}
}
}

View File

@ -1134,7 +1134,9 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
$this->aliases,
$storage->template_types,
$this->type_aliases
)
),
null,
$storage->template_types
);
} catch (TypeParseTreeException $e) {
$storage->docblock_issues[] = new InvalidDocblock(

View File

@ -3647,6 +3647,38 @@ class ClassTemplateExtendsTest extends TestCase
}
}'
],
'nestedTemplateExtends' => [
'<?php
namespace Foo;
interface IBaseViewData {}
/**
* @template TViewData
*/
class BaseModel {}
/**
* @template TViewData as IBaseViewData
* @template TModel as BaseModel<TViewData>
*/
abstract class BaseRepository {}
class StudentViewData implements IBaseViewData {}
class TeacherViewData implements IBaseViewData {}
/** @extends BaseModel<StudentViewData> */
class StudentModel extends BaseModel {}
/** @extends BaseModel<TeacherViewData> */
class TeacherModel extends BaseModel {}
/** @extends BaseRepository<StudentViewData, StudentModel> */
class StudentRepository extends BaseRepository {}
/** @extends BaseRepository<TeacherViewData, TeacherModel> */
class TeacherRepository extends BaseRepository {}'
],
];
}
@ -4872,6 +4904,36 @@ class ClassTemplateExtendsTest extends TestCase
}',
'error_message' => 'LessSpecificReturnStatement'
],
'nestedTemplateExtendsInvalid' => [
'<?php
namespace Foo;
interface IBaseViewData {}
/**
* @template TViewData
*/
class BaseModel {}
/**
* @template TViewData as IBaseViewData
* @template TModel as BaseModel<TViewData>
*/
abstract class BaseRepository {}
class StudentViewData implements IBaseViewData {}
class TeacherViewData implements IBaseViewData {}
/** @extends BaseModel<StudentViewData> */
class StudentModel extends BaseModel {}
/** @extends BaseModel<TeacherViewData> */
class TeacherModel extends BaseModel {}
/** @extends BaseRepository<StudentViewData, TeacherModel> */
class StudentRepository extends BaseRepository {}',
'error_message' => 'InvalidTemplateParam'
],
];
}
}