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

Improve robustness of template checks

This commit is contained in:
Brown 2019-06-25 13:20:30 -04:00
parent 4f9c040a15
commit 91686bef4b
4 changed files with 73 additions and 37 deletions

View File

@ -1597,6 +1597,14 @@ class ClassAnalyzer extends ClassLikeAnalyzer
if ($return_type && $class_storage->template_type_extends) {
$generic_params = [];
$declaring_method_id = $codebase->methods->getDeclaringMethodId($analyzed_method_id);
if ($declaring_method_id) {
$declaring_class_name = explode('::', $declaring_method_id)[0];
$class_storage = $codebase->classlike_storage_provider->get($declaring_class_name);
}
$class_template_params = MethodCallAnalyzer::getClassTemplateParams(
$codebase,
$class_storage,

View File

@ -5,6 +5,7 @@ use PhpParser;
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
use Psalm\Internal\Analyzer\CommentAnalyzer;
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Analyzer\TraitAnalyzer;
use Psalm\Internal\Analyzer\TypeAnalyzer;
@ -161,6 +162,31 @@ class ReturnAnalyzer
$local_return_type = $source->getLocalReturnType($storage->return_type);
if ($storage instanceof \Psalm\Storage\MethodStorage) {
list($fq_class_name, $method_name) = explode('::', $cased_method_id);
if ($fq_class_name !== $context->self) {
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
$found_generic_params = MethodCallAnalyzer::getClassTemplateParams(
$codebase,
$class_storage,
$fq_class_name,
strtolower($method_name),
null,
null
);
if ($found_generic_params) {
$local_return_type = clone $local_return_type;
$local_return_type->replaceTemplateTypesWithArgTypes(
$found_generic_params
);
}
}
}
if ($local_return_type->isGenerator() && $storage->has_yield) {
return null;
}

View File

@ -886,7 +886,7 @@ class ClassTemplateExtendsTest extends TestCase
public function valid(): bool { return false; }
}',
],
'traitUse' => [
'traitUseNotExtended' => [
'<?php
/**
* @template T
@ -2195,6 +2195,37 @@ class ClassTemplateExtendsTest extends TestCase
}
}'
],
'templatedInterfaceGetIteratorIteration' => [
'<?php
namespace NS;
/**
* @template TKey
* @template TValue
* @template-extends \IteratorAggregate<TKey, TValue>
*/
interface ICollection extends \IteratorAggregate {
/** @return \Traversable<TKey,TValue> */
public function getIterator();
}
class Collection implements ICollection {
/** @var array */
private $data;
public function __construct(array $data) {
$this->data = $data;
}
/** @psalm-suppress LessSpecificImplementedReturnType */
public function getIterator(): \Traversable {
return new \ArrayIterator($this->data);
}
}
/** @var ICollection<string, int> */
$c = new Collection(["a" => 1]);
foreach ($c->getIterator() as $k => $v) { atan($v); strlen($k); }',
],
];
}

View File

@ -84,7 +84,7 @@ class ClassTemplateTest extends TestCase
* @template T as object
*/
class Foo {
/** @var T::class */
/** @var class-string<T> */
public $T;
/**
@ -322,11 +322,11 @@ class ClassTemplateTest extends TestCase
],
'noRepeatedTypeException' => [
'<?php
/** @template T */
/** @template T as object */
class Foo
{
/**
* @psalm-var class-string
* @psalm-var class-string<T>
*/
private $type;
@ -334,14 +334,14 @@ class ClassTemplateTest extends TestCase
private $items;
/**
* @param class-string $type
* @template-typeof T $type
* @param class-string<T> $type
*/
public function __construct(string $type)
{
if (!in_array($type, [A::class, B::class], true)) {
throw new \InvalidArgumentException;
}
$this->type = $type;
$this->items = [];
}
@ -372,6 +372,7 @@ class ClassTemplateTest extends TestCase
*/
private function ensureFoo(array $items): Foo
{
/** @var class-string<T> */
$type = $items[0] instanceof A ? A::class : B::class;
return new Foo($type);
}
@ -441,36 +442,6 @@ class ClassTemplateTest extends TestCase
foreach ($c as $k => $v) { atan($v); strlen($k); }',
],
'templatedInterfaceGetIteratorIteration' => [
'<?php
namespace NS;
/**
* @template TKey
* @template TValue
*/
interface ICollection extends \IteratorAggregate {
/** @return \Traversable<TKey,TValue> */
public function getIterator();
}
class Collection implements ICollection {
/** @var array */
private $data;
public function __construct(array $data) {
$this->data = $data;
}
/** @psalm-suppress LessSpecificImplementedReturnType */
public function getIterator(): \Traversable {
return new \ArrayIterator($this->data);
}
}
/** @var ICollection<string, int> */
$c = new Collection(["a" => 1]);
foreach ($c->getIterator() as $k => $v) { atan($v); strlen($k); }',
],
'allowTemplatedIntersectionToExtend' => [
'<?php
interface Foo {}
@ -1172,7 +1143,7 @@ class ClassTemplateTest extends TestCase
*
* @param class-string<Q> $obj
*
* @return array<I, V>
* @return array<I, V|Q>
*/
public function appendProperty(string $obj): array
{