Skip to content

Commit

Permalink
Merge pull request #9658 from klimick/fix-list-template-replacement
Browse files Browse the repository at this point in the history
Fix list<T> template replacement
  • Loading branch information
orklah committed Apr 16, 2023
2 parents 3e50f68 + f015372 commit 2a6d9a8
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 6 deletions.
1 change: 0 additions & 1 deletion psalm-baseline.xml
Expand Up @@ -549,7 +549,6 @@
<PossiblyUndefinedIntArrayOffset>
<code><![CDATA[$this->properties[0]]]></code>
<code><![CDATA[$this->properties[0]]]></code>
<code><![CDATA[$this->properties[0]]]></code>
</PossiblyUndefinedIntArrayOffset>
<PossiblyUnusedMethod>
<code>getList</code>
Expand Down
48 changes: 43 additions & 5 deletions src/Psalm/Type/Atomic/TKeyedArray.php
Expand Up @@ -149,15 +149,24 @@ public function isSealed(): bool
return $this->fallback_params === null;
}

/**
* @psalm-assert-if-true list{Union} $this->properties
* @psalm-assert-if-true list{Union, Union} $this->fallback_params
*/
public function isGenericList(): bool
{
return $this->is_list
&& count($this->properties) === 1
&& $this->fallback_params
&& $this->properties[0]->equals($this->fallback_params[1], true, true, false);
}

public function getId(bool $exact = true, bool $nested = false): string
{
$property_strings = [];

if ($this->is_list) {
if (count($this->properties) === 1
&& $this->fallback_params
&& $this->properties[0]->equals($this->fallback_params[1], true, true, false)
) {
if ($this->isGenericList()) {
$t = $this->properties[0]->possibly_undefined ? 'list' : 'non-empty-list';
return "$t<".$this->fallback_params[1]->getId($exact).'>';
}
Expand Down Expand Up @@ -415,7 +424,7 @@ public function getGenericArrayType(bool $allow_non_empty = true, ?string $list_

public function isNonEmpty(): bool
{
if ($this->is_list) {
if ($this->isGenericList()) {
return !$this->properties[0]->possibly_undefined;
}
foreach ($this->properties as $property) {
Expand Down Expand Up @@ -503,6 +512,35 @@ public function replaceTemplateTypesWithStandins(
bool $add_lower_bound = false,
int $depth = 0
): self {
if ($input_type instanceof TKeyedArray
&& $input_type->is_list
&& $input_type->isSealed()
&& $this->isGenericList()
) {
$replaced_list_type = $this
->getGenericArrayType()
->replaceTemplateTypesWithStandins(
$template_result,
$codebase,
$statements_analyzer,
$input_type->getGenericArrayType(),
$input_arg_offset,
$calling_class,
$calling_function,
$replace,
$add_lower_bound,
$depth,
)
->type_params[1]
->setPossiblyUndefined(!$this->isNonEmpty());

$cloned = clone $this;
$cloned->properties = [$replaced_list_type];
$cloned->fallback_params = [$this->fallback_params[1], $replaced_list_type];

return $cloned;
}

$properties = $this->properties;

foreach ($properties as $offset => $property) {
Expand Down
29 changes: 29 additions & 0 deletions tests/FunctionCallTest.php
Expand Up @@ -17,6 +17,35 @@ class FunctionCallTest extends TestCase
public function providerValidCodeParse(): iterable
{
return [
'inferGenericListFromTuple' => [
'code' => '<?php
/**
* @template A
* @param list<A> $list
* @return list<A>
*/
function testList(array $list): array { return $list; }
/**
* @template A
* @param non-empty-list<A> $list
* @return non-empty-list<A>
*/
function testNonEmptyList(array $list): array { return $list; }
/**
* @template A of list<mixed>
* @param A $list
* @return A
*/
function testGenericList(array $list): array { return $list; }
$list = testList([1, 2, 3]);
$nonEmptyList = testNonEmptyList([1, 2, 3]);
$genericList = testGenericList([1, 2, 3]);',
'assertions' => [
'$list===' => 'list<1|2|3>',
'$nonEmptyList===' => 'non-empty-list<1|2|3>',
'$genericList===' => 'list{1, 2, 3}',
],
],
'countShapedArrays' => [
'code' => '<?php
/** @var array{a?: int} */
Expand Down

0 comments on commit 2a6d9a8

Please sign in to comment.