1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Fix #2928 - properly expand out skipped template inheritance

This commit is contained in:
Matthew Brown 2020-03-06 19:24:47 -05:00
parent 352bd3f5c5
commit b999037263
5 changed files with 122 additions and 45 deletions

View File

@ -1144,7 +1144,7 @@ class MethodAnalyzer extends FunctionLikeAnalyzer
/**
* @param array<string, array<int|string, Type\Union>> $template_type_extends
*/
private static function transformTemplates(
public static function transformTemplates(
array $template_type_extends,
string $base_class_name,
Type\Union $templated_type,

View File

@ -1733,8 +1733,6 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
}
}
$static_template_types = $static_class_storage->template_types;
foreach ($template_types as $type_name => $type_map) {
foreach ($type_map as list($type)) {
foreach ($candidate_class_storages as $candidate_class_storage) {
@ -1742,45 +1740,15 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
&& isset($e[$candidate_class_storage->name][$type_name])
&& !isset($class_template_params[$type_name][$candidate_class_storage->name])
) {
$input_type_extends = $e[$candidate_class_storage->name][$type_name];
$output_type_extends = null;
foreach ($input_type_extends->getAtomicTypes() as $type_extends_atomic) {
if ($type_extends_atomic instanceof Type\Atomic\TTemplateParam) {
if ($static_class_storage->name === $type_extends_atomic->defining_class
&& isset($static_template_types[$type_extends_atomic->param_name])
) {
if (!$output_type_extends) {
$output_type_extends = new Type\Union([$type_extends_atomic]);
} else {
$output_type_extends = Type::combineUnionTypes(
new Type\Union([$type_extends_atomic]),
$output_type_extends
);
}
} elseif (!$output_type_extends) {
$output_type_extends = $type_extends_atomic->as;
} else {
$output_type_extends = Type::combineUnionTypes(
$type_extends_atomic->as,
$output_type_extends
);
}
} else {
if (!$output_type_extends) {
$output_type_extends = new Type\Union([$type_extends_atomic]);
} else {
$output_type_extends = Type::combineUnionTypes(
new Type\Union([$type_extends_atomic]),
$output_type_extends
);
}
}
}
$class_template_params[$type_name][$candidate_class_storage->name] = [
$output_type_extends
new Type\Union(
self::expandType(
$e[$candidate_class_storage->name][$type_name],
$e,
$static_class_storage->name,
$static_class_storage->template_types
)
)
];
}
}
@ -1796,6 +1764,41 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
return $class_template_params;
}
/**
* @param array<string, array<int|string, Type\Union>> $e
* @return non-empty-list<Type\Atomic>
*/
private static function expandType(
Type\Union $input_type_extends,
array $e,
string $static_fq_class_name,
?array $static_template_types
) : array {
$output_type_extends = [];
foreach ($input_type_extends->getAtomicTypes() as $type_extends_atomic) {
if ($type_extends_atomic instanceof Type\Atomic\TTemplateParam
&& ($static_fq_class_name !== $type_extends_atomic->defining_class
|| !isset($static_template_types[$type_extends_atomic->param_name]))
&& isset($e[$type_extends_atomic->defining_class][$type_extends_atomic->param_name])
) {
$output_type_extends = array_merge(
$output_type_extends,
self::expandType(
$e[$type_extends_atomic->defining_class][$type_extends_atomic->param_name],
$e,
$static_fq_class_name,
$static_template_types
)
);
} else {
$output_type_extends[] = $type_extends_atomic;
}
}
return $output_type_extends;
}
/**
* Check properties accessed with magic getters and setters.
* If `@psalm-seal-properties` is set, they must be defined.

View File

@ -570,9 +570,7 @@ class Analyzer
public function loadCachedResults(ProjectAnalyzer $project_analyzer)
{
$codebase = $project_analyzer->getCodebase();
if ($codebase->diff_methods
&& (!$codebase->collect_references || $codebase->server_mode)
) {
if ($codebase->diff_methods) {
$this->analyzed_methods = $codebase->file_reference_provider->getAnalyzedMethods();
$this->existing_issues = $codebase->file_reference_provider->getExistingIssues();
$file_maps = $codebase->file_reference_provider->getFileMaps();

View File

@ -324,7 +324,7 @@ class Populator
}
if ((count($overridden_method_ids) === 1
|| $candidate_overridden_ids)
|| $candidate_overridden_ids)
&& $method_storage->signature_return_type
&& !$method_storage->signature_return_type->isVoid()
&& ($method_storage->return_type === $method_storage->signature_return_type

View File

@ -3119,6 +3119,82 @@ class ClassTemplateExtendsTest extends TestCase
}
}'
],
'implementsTemplatedOnce' => [
'<?php
/**
* @template T1
*/
interface A {
/** @return T1 */
public function get();
}
/**
* @template T3
* @implements A<T3>
*/
class C implements A {
/** @var T3 */
private $val;
/**
* @psalm-param T3 $val
*/
public function __construct($val) {
$this->val = $val;
}
public function get() {
return $this->val;
}
}
$foo = (new C("foo"))->get();',
[
'$foo' => 'string',
]
],
'implementsTemplatedTwice' => [
'<?php
/**
* @template T1
*/
interface A {
/** @return T1 */
public function get();
}
/**
* @template T2
* @extends A<T2>
*/
interface B extends A {}
/**
* @template T3
* @implements B<T3>
*/
class C implements B {
/** @var T3 */
private $val;
/**
* @psalm-param T3 $val
*/
public function __construct($val) {
$this->val = $val;
}
public function get() {
return $this->val;
}
}
$foo = (new C("foo"))->get();',
[
'$foo' => 'string',
]
],
];
}