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:
parent
1309eece2e
commit
5ef82b53f6
@ -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]
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user