From b26deb434d6ff09cd53b382d3faf77018f5dd66c Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Sat, 8 Feb 2020 11:17:24 -0500 Subject: [PATCH] Fix #2771 - replace empty params with generic equivalents Ref #2755 --- src/Psalm/Internal/Analyzer/TypeAnalyzer.php | 10 ++ tests/Template/ClassTemplateTest.php | 143 ++++++++++++++++--- 2 files changed, 131 insertions(+), 22 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/TypeAnalyzer.php b/src/Psalm/Internal/Analyzer/TypeAnalyzer.php index 4002000a7c3..110f34c3275 100644 --- a/src/Psalm/Internal/Analyzer/TypeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/TypeAnalyzer.php @@ -2014,6 +2014,16 @@ private static function isMatchingTypeContainedBy( $container_param = $container_type_part->type_params[$i]; if ($input_param->isEmpty()) { + if (!$atomic_comparison_result->replacement_atomic_type) { + $atomic_comparison_result->replacement_atomic_type = clone $input_type_part; + } + + if ($atomic_comparison_result->replacement_atomic_type instanceof TGenericObject) { + /** @psalm-suppress PropertyTypeCoercion */ + $atomic_comparison_result->replacement_atomic_type->type_params[$i] + = clone $container_param; + } + continue; } diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index b007c5dccbe..3900a8e428a 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -331,7 +331,7 @@ public function __construct(array $data) { * @return ArrayCollection */ public function map(Closure $func) { - return new static(array_map($func, $this->data)); + return new static(array_map($func, $this->data)); } } @@ -428,13 +428,13 @@ class I {} $c = new Collection; $c->filter( - /** @param Collection $elt */ - function(Collection $elt): bool { return (bool) rand(0,1); } + /** @param Collection $elt */ + function(Collection $elt): bool { return (bool) rand(0,1); } ); $c->filter( - /** @param Collection $elt */ - function(Collection $elt): bool { return true; } + /** @param Collection $elt */ + function(Collection $elt): bool { return true; } );', ], 'templatedInterfaceIteration' => [ @@ -1128,8 +1128,8 @@ class Converter public $records; /** - * @param array $records - */ + * @param array $records + */ public function __construct(array $records) { $this->records = $records; } @@ -1146,7 +1146,7 @@ private function appender(object $obj2): array $arr = []; foreach ($this->records as $key => $obj) { if (rand(0, 1)) { - $obj = $obj2; + $obj = $obj2; } $arr[$key] = $obj; } @@ -1211,7 +1211,7 @@ private function appender(string $obj2): array $arr = []; foreach ($this->records as $key => $obj) { if (rand(0, 1)) { - $obj = new $obj2; + $obj = new $obj2; } $arr[$key] = $obj; } @@ -1852,23 +1852,22 @@ public function __construct($closure) {} class Bar { - /** @var Foo */ - private $FooArray; + /** @var Foo */ + private $FooArray; - public function __construct() - { - $this->FooArray = new Foo(function(string $s): array { - /** @psalm-suppress MixedAssignment */ - $json = \json_decode($s, true); + public function __construct() { + $this->FooArray = new Foo(function(string $s): array { + /** @psalm-suppress MixedAssignment */ + $json = \json_decode($s, true); - if (! \is_array($json)) { - return []; - } + if (! \is_array($json)) { + return []; + } - return $json; - }); + return $json; + }); - takesFooArray($this->FooArray); + takesFooArray($this->FooArray); } } @@ -2275,6 +2274,55 @@ public function __construct(string $type) { } }' ], + 'newGenericBecomesPropertyType' => [ + ' */ + public ArrayCollection $b_collection; + + public function __construct() { + $this->b_collection = new ArrayCollection([]); + $this->b_collection->add(5, new B()); + } + } + + /** + * @psalm-template TKey + * @psalm-template T + */ + class ArrayCollection + { + /** + * An array containing the entries of this collection. + * + * @psalm-var array + * @var array + */ + private $elements = []; + + /** + * Initializes a new ArrayCollection. + * + * @param array $elements + * + * @psalm-param array $elements + */ + public function __construct(array $elements = []) + { + $this->elements = $elements; + } + + /** + * @param TKey $key + * @param T $t + */ + public function add($key, $t) : void { + $this->elements[$key] = $t; + } + }' + ], ]; } @@ -2722,6 +2770,57 @@ public static function foo($t) : void {} }', 'error_message' => 'UndefinedDocblockClass' ], + 'newGenericBecomesPropertyType' => [ + ' */ + public ArrayCollection $b_collection; + + public function __construct() { + $this->b_collection = new ArrayCollection([]); + $this->b_collection->add(5, new C()); + } + } + + /** + * @psalm-template TKey + * @psalm-template T + */ + class ArrayCollection + { + /** + * An array containing the entries of this collection. + * + * @psalm-var array + * @var array + */ + private $elements = []; + + /** + * Initializes a new ArrayCollection. + * + * @param array $elements + * + * @psalm-param array $elements + */ + public function __construct(array $elements = []) + { + $this->elements = $elements; + } + + /** + * @param TKey $key + * @param T $t + */ + public function add($key, $t) : void { + $this->elements[$key] = $t; + } + }', + 'error_message' => 'InvalidArgument' + ], ]; } }