Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent redundant messages about missing iterable types in conditional target/subject #1314

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/Rules/MissingTypehintCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Accessory\AccessoryType;
use PHPStan\Type\CallableType;
use PHPStan\Type\ConditionalType;
use PHPStan\Type\ConditionalTypeForParameter;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Generic\TemplateTypeHelper;
Expand All @@ -21,6 +23,7 @@
use PHPStan\Type\TypeWithClassName;
use Traversable;
use function array_keys;
use function array_merge;
use function in_array;
use function sprintf;

Expand Down Expand Up @@ -71,6 +74,15 @@ public function getIterableTypesWithMissingValueTypehint(Type $type): array
if ($type instanceof AccessoryType) {
return $type;
}
if ($type instanceof ConditionalType || $type instanceof ConditionalTypeForParameter) {
$iterablesWithMissingValueTypehint = array_merge(
$iterablesWithMissingValueTypehint,
$this->getIterableTypesWithMissingValueTypehint($type->getIf()),
$this->getIterableTypesWithMissingValueTypehint($type->getElse()),
);

return $type;
}
Comment on lines +77 to +85
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not overly excited by this. An alternative would be to get the resolved type, but that might skip over types that disappear after normalization.

if ($type->isIterable()->yes()) {
$iterableValue = $type->getIterableValueType();
if ($iterableValue instanceof MixedType && !$iterableValue->isExplicitMixed()) {
Expand Down
10 changes: 10 additions & 0 deletions src/Type/ConditionalType.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ public function getTarget(): Type
return $this->target;
}

public function getIf(): Type
{
return $this->if;
}

public function getElse(): Type
{
return $this->else;
}

public function isNegated(): bool
{
return $this->negated;
Expand Down
10 changes: 10 additions & 0 deletions src/Type/ConditionalTypeForParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ public function getTarget(): Type
return $this->target;
}

public function getIf(): Type
{
return $this->if;
}

public function getElse(): Type
{
return $this->else;
}

public function isNegated(): bool
{
return $this->negated;
Expand Down
16 changes: 10 additions & 6 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -602,15 +602,13 @@ public function testBug6896(): void
}

$errors = $this->runAnalyse(__DIR__ . '/data/bug-6896.php');
$this->assertCount(4, $errors);
$this->assertCount(3, $errors);
$this->assertSame('Generic type IteratorIterator<(int|string), mixed> in PHPDoc tag @return does not specify all template types of class IteratorIterator: TKey, TValue, TIterator', $errors[0]->getMessage());
$this->assertSame(38, $errors[0]->getLine());
$this->assertSame('Method Bug6896\RandHelper::getPseudoRandomWithUrl() return type has no value type specified in iterable type array.', $errors[1]->getMessage());
$this->assertSame('Method Bug6896\RandHelper::getPseudoRandomWithUrl() return type with generic class Bug6896\XIterator does not specify its types: TKey, TValue', $errors[1]->getMessage());
$this->assertSame(38, $errors[1]->getLine());
$this->assertSame('Method Bug6896\RandHelper::getPseudoRandomWithUrl() return type with generic class Bug6896\XIterator does not specify its types: TKey, TValue', $errors[2]->getMessage());
$this->assertSame(38, $errors[2]->getLine());
$this->assertSame('Method Bug6896\RandHelper::getPseudoRandomWithUrl() should return array<TRandKey of (int|string), TRandVal>|Bug6896\XIterator<TRandKey of (int|string), TRandVal>|(iterable<TRandKey of (int|string), TRandVal>&LimitIterator)|IteratorIterator<TRandKey of (int|string), TRandVal> but returns TRandList of array<TRandKey of (int|string), TRandVal>|Traversable<TRandKey of (int|string), TRandVal>.', $errors[3]->getMessage());
$this->assertSame(42, $errors[3]->getLine());
$this->assertSame('Method Bug6896\RandHelper::getPseudoRandomWithUrl() should return array<TRandKey of (int|string), TRandVal>|Bug6896\XIterator<TRandKey of (int|string), TRandVal>|(iterable<TRandKey of (int|string), TRandVal>&LimitIterator)|IteratorIterator<TRandKey of (int|string), TRandVal> but returns TRandList of array<TRandKey of (int|string), TRandVal>|Traversable<TRandKey of (int|string), TRandVal>.', $errors[2]->getMessage());
$this->assertSame(42, $errors[2]->getLine());
}

public function testBug6940(): void
Expand Down Expand Up @@ -773,6 +771,12 @@ public function testBug7214(): void
$this->assertSame(6, $errors[0]->getLine());
}

public function testBug7215(): void
{
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7215.php');
$this->assertNoErrors($errors);
}

/**
* @param string[]|null $allAnalysedFiles
* @return Error[]
Expand Down
26 changes: 26 additions & 0 deletions tests/PHPStan/Analyser/data/bug-7215.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types = 1);

namespace Bug7215;

use function PHPStan\Testing\assertType;

/**
* @template T
* @param array<T,mixed> $array
* @return (T is int ? ($array is non-empty-array ? non-empty-list<numeric-string> : list<numeric-string>) : ($array is non-empty-array ? non-empty-list<numeric-string> : list<string>))
*/
function keysAsString(array $array): array
{
$keys = [];

foreach ($array as $k => $_) {
$keys[] = (string)$k;
}

return $keys;
}

function () {
assertType('array<int, numeric-string>', keysAsString([]));
assertType('non-empty-array<int, numeric-string>', keysAsString(['' => '']));
};