Skip to content

Commit

Permalink
Fix wrong positives about templates in conditional types
Browse files Browse the repository at this point in the history
  • Loading branch information
KmeCnin authored Mar 9, 2023
1 parent d943d58 commit 1292afe
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 7 deletions.
15 changes: 12 additions & 3 deletions src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\VerbosityLevel;
use function array_key_exists;
use function count;
use function sprintf;
use function substr;

Expand All @@ -24,8 +25,6 @@ class ConditionalReturnTypeRuleHelper
*/
public function check(ParametersAcceptor $acceptor): array
{
$templateTypeMap = $acceptor->getTemplateTypeMap();

$conditionalTypes = [];
$parametersByName = [];
foreach ($acceptor->getParameters() as $parameter) {
Expand Down Expand Up @@ -55,7 +54,17 @@ public function check(ParametersAcceptor $acceptor): array
if ($subjectType instanceof StaticType) {
continue;
}
if (!$subjectType instanceof TemplateType || $templateTypeMap->getType($subjectType->getName()) === null) {
$templateTypes = [];
TypeTraverser::map($subjectType, static function (Type $type, callable $traverse) use (&$templateTypes): Type {
if ($type instanceof TemplateType) {
$templateTypes[] = $type;
return $type;
}

return $traverse($type);
});

if (count($templateTypes) === 0) {
$errors[] = RuleErrorBuilder::message(sprintf('Conditional return type uses subject type %s which is not part of PHPDoc @template tags.', $subjectType->describe(VerbosityLevel::typeOnly())))->build();
continue;
}
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,8 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8917.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/ds-copy.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/trait-type-alias.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8609.php');
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-8609-function.php');
}

/**
Expand Down
38 changes: 38 additions & 0 deletions tests/PHPStan/Analyser/data/bug-8609.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Bug8609TypeInference;

use function PHPStan\Testing\assertType;

/**
* @template T of bool
*/
class Foo
{

/** @return (T is true ? 'foo' : 'bar') */
public function doFoo(): string
{

}

}

class Bar
{

/**
* @param Foo<true> $f
* @param Foo<false> $g
* @param Foo<bool> $h
* @param Foo $i
*/
public function doFoo(Foo $f, Foo $g, Foo $h, Foo $i): void
{
assertType('\'foo\'', $f->doFoo());
assertType('\'bar\'', $g->doFoo());
assertType('\'bar\'|\'foo\'', $h->doFoo());
assertType('\'bar\'|\'foo\'', $i->doFoo());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,9 @@ public function testRule(): void
]);
}

public function testBug8609(): void
{
$this->analyse([__DIR__ . '/data/bug-8609-function.php'], []);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ public function testRule(): void
'Conditional return type uses subject type stdClass which is not part of PHPDoc @template tags.',
48,
],
[
'Conditional return type uses subject type TAboveClass which is not part of PHPDoc @template tags.',
57,
],
[
'Conditional return type references unknown parameter $j.',
65,
Expand Down Expand Up @@ -80,4 +76,9 @@ public function testBug8284(): void
]);
}

public function testBug8609(): void
{
$this->analyse([__DIR__ . '/data/bug-8609.php'], []);
}

}
18 changes: 18 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/data/bug-8609-function.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Bug8609Function;

use function PHPStan\Testing\assertType;

/**
* @template T of list<string>|list<list<string>>
* @param T $bar
*
* @return (T[0] is string ? array{T} : T)
*/
function foo(array $bar) : array{ return is_string($bar[0]) ? [$bar] : $bar; }

function(): void {
assertType('array{array{string, string}}', foo(['foo', 'bar']));
assertType('array{array{string, string}, array{string, string}}', foo([['foo','bar'],['xyz','asd']]));
};
34 changes: 34 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/data/bug-8609.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PhpDoc\data;

/**
* @template T of int
*/
class A {
/** @var T */
private $value;

/** @param T $value */
public function __construct($value) {
$this->value = $value;
}

/**
* @template C of int
*
* @param C $coefficient
*
* @return (
* T is positive-int
* ? (C is positive-int ? positive-int : negative-int)
* : T is negative-int
* ? (C is positive-int ? negative-int : positive-int)
* : (T is 0 ? 0 : int)
* )
* )
*/
public function multiply(int $coefficient): int {
return $this->value * $coefficient;
}
}

0 comments on commit 1292afe

Please sign in to comment.