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

Preserve intersections when expanding templated types

Fixes #4043
This commit is contained in:
Brown 2020-08-25 14:12:57 -04:00
parent 346d475f55
commit 6e1218065d
2 changed files with 117 additions and 2 deletions

View File

@ -108,8 +108,7 @@ class UnionTemplateHandler
}
if ($atomic_type instanceof Atomic\TTemplateParam
&& ($param_name_key = strpos($key, '&') ? $key : $atomic_type->param_name)
&& isset($template_result->template_types[$param_name_key][$atomic_type->defining_class])
&& isset($template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class])
) {
$a = self::handleTemplateParamStandin(
$atomic_type,
@ -489,6 +488,38 @@ class UnionTemplateHandler
$param_name_key = $key;
}
$extra_types = [];
if ($atomic_type->extra_types) {
foreach ($atomic_type->extra_types as $extra_type) {
$extra_type = self::replaceTemplateTypesWithStandins(
new \Psalm\Type\Union([$extra_type]),
$template_result,
$codebase,
$statements_analyzer,
$input_type,
$input_arg_offset,
$calling_class,
$calling_function,
$replace,
$add_upper_bound,
$depth + 1
);
if ($extra_type->isSingle()) {
$extra_type = array_values($extra_type->getAtomicTypes())[0];
if ($extra_type instanceof Atomic\TNamedObject
|| $extra_type instanceof Atomic\TTemplateParam
|| $extra_type instanceof Atomic\TIterable
|| $extra_type instanceof Atomic\TObjectWithProperties
) {
$extra_types[] = $extra_type;
}
}
}
}
if ($replace) {
$atomic_types = [];
@ -675,6 +706,10 @@ class UnionTemplateHandler
];
}
foreach ($atomic_types as $atomic_type) {
$atomic_type->extra_types = $extra_types;
}
return $atomic_types;
}

View File

@ -4388,6 +4388,86 @@ class ClassTemplateExtendsTest extends TestCase
abstract public function foo($param): void;
}'
],
'extendAndImplementedTemplatedProperty' => [
'<?php
interface Mock {}
abstract class A {}
class B extends A {}
class BMock extends B {}
/** @template T of A */
abstract class ATestCase {
/** @var T */
protected Mock $foo;
/** @param T $foo */
public function __construct(A $foo) {
$this->foo = $foo;
}
}
/** @extends ATestCase<B> */
class BTestCase extends ATestCase {
public function getFoo(): B {
return $this->foo;
}
}
new BTestCase(new BMock());'
],
'extendAndImplementedTemplatedIntersectionProperty' => [
'<?php
interface Mock {
function foo():void;
}
abstract class A {}
class B extends A {}
/** @template T of A */
abstract class ATestCase {
/** @var T&Mock */
protected Mock $obj;
/** @param T&Mock $obj */
public function __construct(Mock $obj) {
$this->obj = $obj;
}
}
/** @extends ATestCase<B> */
class BTestCase extends ATestCase {
public function getFoo(): void {
$this->obj->foo();
}
}'
],
'extendAndImplementedTemplatedIntersectionReceives' => [
'<?php
interface Mock {
function foo():void;
}
abstract class A {}
class B extends A {}
class BMock extends B implements Mock {
public function foo(): void {}
}
/** @template T of A */
abstract class ATestCase {
/** @var T&Mock */
protected Mock $obj;
/** @param T&Mock $obj */
public function __construct(Mock $obj) {
$this->obj = $obj;
}
}
/** @extends ATestCase<B> */
class BTestCase extends ATestCase {}
new BTestCase(new BMock());'
],
];
}