Skip to content
8 changes: 7 additions & 1 deletion src/PhpDoc/TypeNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
return new NeverType(true);

case 'list':
return new ErrorType();
return new ArrayType(new IntegerType(), new MixedType());
}

if ($nameScope->getClassName() !== null) {
Expand Down Expand Up @@ -313,6 +313,12 @@ private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $na
return new ArrayType($genericTypes[0], $genericTypes[1]);
}

} elseif ($mainTypeName === 'list') {
if (count($genericTypes) === 1) { // list<ValueType>
return new ArrayType(new IntegerType(), $genericTypes[0]);
}

return new ErrorType();
} elseif ($mainTypeName === 'iterable') {
if (count($genericTypes) === 1) { // iterable<ValueType>
return new IterableType(new MixedType(true), $genericTypes[0]);
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9783,6 +9783,11 @@ public function dataPhpDocInheritanceParameterRemapping(): array
return $this->gatherAssertTypes(__DIR__ . '/data/inheritdoc-parameter-remapping.php');
}

public function dataListType(): array
{
return $this->gatherAssertTypes(__DIR__ . '/data/list-type.php');
}

/**
* @dataProvider dataBug2574
* @dataProvider dataBug2577
Expand All @@ -9803,6 +9808,7 @@ public function dataPhpDocInheritanceParameterRemapping(): array
* @dataProvider dataBug2648
* @dataProvider dataBug2740
* @dataProvider dataPhpDocInheritanceParameterRemapping
* @dataProvider dataListType
* @param ConstantStringType $expectedType
* @param Type $actualType
*/
Expand Down
97 changes: 97 additions & 0 deletions tests/PHPStan/Analyser/data/list-type.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace ListType;

use function PHPStan\Analyser\assertType;

class Foo
{
/** @param list $list */
public function directAssertion($list): void
{
assertType('array<int, mixed>', $list);
}

/** @param list $list */
public function directAssertionParamHint(array $list): void
{
assertType('array<int, mixed>', $list);
}

/** @param list $list */
public function directAssertionNullableParamHint(array $list = null): void
{
assertType('array<int, mixed>|null', $list);
}

/** @param list<\DateTime> $list */
public function directAssertionObjectParamHint($list): void
{
assertType('array<int, DateTime>', $list);
}

public function withoutGenerics(): void
{
/** @var list $list */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should at least once typehint the type as method parameter and assert it right at the beginning of the body. If you assert the variable only after some changes (array appending), you have no guarantees about the state right after $list = [];.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I added some test cases with direct assertions. It should be ok

$list = [];
$list[] = '1';
$list[] = true;
$list[] = new \stdClass();
assertType('array<int, mixed>', $list);
}


public function withMixedType(): void
{
/** @var list<mixed> $list */
$list = [];
$list[] = '1';
$list[] = true;
$list[] = new \stdClass();
assertType('array<int, mixed>', $list);
}

public function withObjectType(): void
{
/** @var list<\DateTime> $list */
$list = [];
$list[] = new \DateTime();
assertType('array<int, DateTime>', $list);
}

/** @return list<scalar> */
public function withScalarGoodContent(): void
{
/** @var list<scalar> $list */
$list = [];
$list[] = '1';
$list[] = true;
assertType('array<int, bool|float|int|string>', $list);
}

public function withNumericKey(): void
{
/** @var list $list */
$list = [];
$list[] = '1';
$list['1'] = true;
assertType('array<int, mixed>', $list);
}

public function withFullListFunctionality(): void
{
// These won't output errors for now but should when list type will be fully implemented
/** @var list $list */
$list = [];
$list[] = '1';
$list[] = '2';
unset($list[0]);//break list behaviour
assertType('array<int, mixed>', $list);

/** @var list $list2 */
$list2 = [];
$list2[2] = '1';//Most likely to create a gap in indexes
assertType('array<int, mixed>', $list2);
}

}