Skip to content

Commit

Permalink
Fix inferring template type from non-empty-string
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Aug 15, 2021
1 parent 35a66a2 commit 6a33de9
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 6 deletions.
6 changes: 5 additions & 1 deletion src/Type/Constant/ConstantStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,11 @@ public function append(self $otherString): self
public function generalize(?GeneralizePrecision $precision = null): Type
{
if ($this->isClassString) {
return new ClassStringType();
if ($precision !== null && $precision->isMoreSpecific()) {
return new ClassStringType();
}

return new StringType();
}

if ($this->getValue() !== '' && $precision !== null && $precision->isMoreSpecific()) {
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Generic/TemplateTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PHPStan\Type\ConstantType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;

Expand Down Expand Up @@ -70,6 +71,10 @@ public static function generalizeType(Type $type): Type
return $type->generalize(GeneralizePrecision::lessSpecific());
}

if ($type->isNonEmptyString()->yes()) {
return new StringType();
}

return $traverse($type);
});
}
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4970.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5322.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/splfixedarray-iterator-types.php');
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5372.php');
}

/**
Expand Down
8 changes: 7 additions & 1 deletion tests/PHPStan/Analyser/data/generics.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,15 @@ function f($a, $b)
*/
function testF($arrayOfInt, $callableOrNull)
{
assertType('array<string&numeric>', f($arrayOfInt, function (int $a): string {
assertType('Closure(int): string&numeric', function (int $a): string {
return (string)$a;
});
assertType('array<string>', f($arrayOfInt, function (int $a): string {
return (string)$a;
}));
assertType('Closure(mixed): string', function ($a): string {
return (string)$a;
});
assertType('array<string>', f($arrayOfInt, function ($a): string {
return (string)$a;
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -563,11 +563,11 @@ public function testArrayReduceCallback(): void
5,
],
[
'Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, int): non-empty-string|null, Closure(string, int): non-empty-string given.',
'Parameter #2 $callback of function array_reduce expects callable(string|null, int): string|null, Closure(string, int): non-empty-string given.',
13,
],
[
'Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, int): non-empty-string|null, Closure(string, int): non-empty-string given.',
'Parameter #2 $callback of function array_reduce expects callable(string|null, int): string|null, Closure(string, int): non-empty-string given.',
22,
],
]);
Expand All @@ -584,11 +584,11 @@ public function testArrayReduceArrowFunctionCallback(): void
5,
],
[
'Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, int): non-empty-string|null, Closure(string, int): non-empty-string given.',
'Parameter #2 $callback of function array_reduce expects callable(string|null, int): string|null, Closure(string, int): non-empty-string given.',
11,
],
[
'Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, int): non-empty-string|null, Closure(string, int): non-empty-string given.',
'Parameter #2 $callback of function array_reduce expects callable(string|null, int): string|null, Closure(string, int): non-empty-string given.',
18,
],
]);
Expand Down
17 changes: 17 additions & 0 deletions tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2029,4 +2029,21 @@ public function testNonEmptyStringVerbosity(): void
]);
}

public function testBug5372(): void
{
if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) {
$this->markTestSkipped('Test requires PHP 7.4.');
}

$this->checkThisOnly = false;
$this->checkNullables = true;
$this->checkUnionTypes = true;
$this->analyse([__DIR__ . '/data/bug-5372.php'], [
[
'Parameter #1 $list of method Bug5372\Foo::takesStrings() expects Bug5372\Collection<int, string>, Bug5372\Collection<int, class-string> given.',
72,
],
]);
}

}
75 changes: 75 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-5372.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php // lint >= 7.4

namespace Bug5372;

use function PHPStan\Testing\assertType;

/**
* @template TKey of array-key
* @template T
*/
class Collection
{

/** @var array<TKey, T> */
private $values;

/**
* @param array<TKey, T> $values
*/
public function __construct(array $values)
{
$this->values = $values;
}

/**
* @template V
*
* @param callable(T): V $callback
*
* @return self<TKey, V>
*/
public function map(callable $callback): self {
return new self(array_map($callback, $this->values));
}

/**
* @template V of string
*
* @param callable(T): V $callback
*
* @return self<TKey, V>
*/
public function map2(callable $callback): self {
return new self(array_map($callback, $this->values));
}
}

class Foo
{

/** @param Collection<int, string> $list */
function takesStrings(Collection $list): void {
echo serialize($list);
}

/** @param class-string $classString */
public function doFoo(string $classString)
{
$col = new Collection(['foo', 'bar']);
assertType('Bug5372\Collection<int, string>', $col);

$newCol = $col->map(static fn(string $var): string => $var . 'bar');
assertType('Bug5372\Collection<int, string>', $newCol);
$this->takesStrings($newCol);

$newCol = $col->map(static fn(string $var): string => $classString);
assertType('Bug5372\Collection<int, string>', $newCol);
$this->takesStrings($newCol);

$newCol = $col->map2(static fn(string $var): string => $classString);
assertType('Bug5372\Collection<int, class-string>', $newCol);
$this->takesStrings($newCol);
}

}

0 comments on commit 6a33de9

Please sign in to comment.