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:
parent
352bd3f5c5
commit
b999037263
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user