mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Better intersection of template types during inheritance check
This commit is contained in:
parent
4ebe4c196f
commit
4f5dfa7350
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user