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

Better intersection of template types during inheritance check

This commit is contained in:
andrew 2023-06-06 16:25:54 +03:00
parent 4ebe4c196f
commit 4f5dfa7350
3 changed files with 47 additions and 14 deletions

View File

@ -8,6 +8,7 @@ use Psalm\CodeLocation;
use Psalm\Codebase;
use Psalm\Context;
use Psalm\Internal\Analyzer\SourceAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\MethodIdentifier;
@ -18,6 +19,8 @@ use Psalm\Internal\Provider\MethodParamsProvider;
use Psalm\Internal\Provider\MethodReturnTypeProvider;
use Psalm\Internal\Provider\MethodVisibilityProvider;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Internal\TypeVisitor\TypeLocalizer;
use Psalm\StatementsSource;
@ -771,6 +774,23 @@ class Methods
if ((!$old_contained_by_new && !$new_contained_by_old)
|| ($old_contained_by_new && $new_contained_by_old)
) {
$found_generic_params = ClassTemplateParamCollector::collect(
$source_analyzer->getCodebase(),
$appearing_fq_class_storage,
$appearing_fq_class_storage,
$appearing_method_name,
null,
true,
);
if ($found_generic_params) {
$overridden_storage_return_type = TemplateInferredTypeReplacer::replace(
$overridden_storage_return_type,
new TemplateResult([], $found_generic_params),
$source_analyzer->getCodebase(),
);
}
$attempted_intersection = null;
if ($old_contained_by_new) { //implicitly $new_contained_by_old as well
try {

View File

@ -739,20 +739,6 @@ abstract class Type
$combined_type = null;
foreach ($type_1->getAtomicTypes() as $type_1_atomic) {
foreach ($type_2->getAtomicTypes() as $type_2_atomic) {
if ($type_1_atomic instanceof TTemplateParam
&& $type_2_atomic instanceof TNamedObject
) {
$intersected_with_template = self::intersectUnionTypes(
$type_1_atomic->as,
new Union([$type_2_atomic]),
$codebase,
);
if ($intersected_with_template && $intersected_with_template->isSingle()) {
$type_1_atomic = $intersected_with_template->getSingleAtomic();
}
}
$intersection_atomic = self::intersectAtomicTypes(
$type_1_atomic,
$type_2_atomic,

View File

@ -4180,6 +4180,33 @@ class ClassTemplateTest extends TestCase
intWithNull(new TWithNull(1));
intWithNull(new NullWithT(1));',
],
'intersectParentTemplateReturnWithConcreteChildReturn' => [
'code' => '<?php
/** @template T */
interface Aggregator
{
/**
* @psalm-param T ...$values
* @psalm-return T
*/
public function aggregate(...$values): mixed;
}
/** @implements Aggregator<int|float|null> */
final class AverageAggregator implements Aggregator
{
public function aggregate(...$values): null|int|float
{
if (!$values) {
return null;
}
return array_sum($values) / count($values);
}
}',
'assertions' => [],
'ignored_issues' => [],
'php_version' => '8.0',
],
];
}