Skip to content

Commit

Permalink
Improve robustness of template checks
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Jun 25, 2019
1 parent 4f9c040 commit 91686be
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 37 deletions.
8 changes: 8 additions & 0 deletions src/Psalm/Internal/Analyzer/ClassAnalyzer.php
Expand Up @@ -1597,6 +1597,14 @@ private function analyzeClassMethod(
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,
Expand Down
26 changes: 26 additions & 0 deletions src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php
Expand Up @@ -5,6 +5,7 @@
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;
Expand Down Expand Up @@ -161,6 +162,31 @@ public static function analyze(

$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;
}
Expand Down
33 changes: 32 additions & 1 deletion tests/Template/ClassTemplateExtendsTest.php
Expand Up @@ -886,7 +886,7 @@ public function rewind(): void {}
public function valid(): bool { return false; }
}',
],
'traitUse' => [
'traitUseNotExtended' => [
'<?php
/**
* @template T
Expand Down Expand Up @@ -2195,6 +2195,37 @@ public function getIterator() {
}
}'
],
'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); }',
],
];
}

Expand Down
43 changes: 7 additions & 36 deletions tests/Template/ClassTemplateTest.php
Expand Up @@ -84,7 +84,7 @@ public function bar() {
* @template T as object
*/
class Foo {
/** @var T::class */
/** @var class-string<T> */
public $T;
/**
Expand Down Expand Up @@ -322,26 +322,26 @@ function takesCollectionOfItems(ArrayCollection $i): void {}
],
'noRepeatedTypeException' => [
'<?php
/** @template T */
/** @template T as object */
class Foo
{
/**
* @psalm-var class-string
* @psalm-var class-string<T>
*/
private $type;
/** @var array<T> */
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 = [];
}
Expand Down Expand Up @@ -372,6 +372,7 @@ public function add($item): void
*/
private function ensureFoo(array $items): Foo
{
/** @var class-string<T> */
$type = $items[0] instanceof A ? A::class : B::class;
return new Foo($type);
}
Expand Down Expand Up @@ -441,36 +442,6 @@ public function getIterator(): \Traversable {
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 {}
Expand Down Expand Up @@ -1172,7 +1143,7 @@ public function __construct(array $records) {
*
* @param class-string<Q> $obj
*
* @return array<I, V>
* @return array<I, V|Q>
*/
public function appendProperty(string $obj): array
{
Expand Down

0 comments on commit 91686be

Please sign in to comment.